update to latest plugin dependencies
This commit is contained in:
parent
66aaa46588
commit
8bbf6e6fc3
|
@ -58,7 +58,7 @@ func (p *pcfMethod) Authenticate(ctx context.Context, client *api.Client) (strin
|
|||
signatureData := &signatures.SignatureData{
|
||||
SigningTime: signingTime,
|
||||
Role: p.roleName,
|
||||
Certificate: string(certBytes),
|
||||
CFInstanceCertContents: string(certBytes),
|
||||
}
|
||||
signature, err := signatures.Sign(pathToClientKey, signatureData)
|
||||
if err != nil {
|
||||
|
@ -66,7 +66,7 @@ func (p *pcfMethod) Authenticate(ctx context.Context, client *api.Client) (strin
|
|||
}
|
||||
data := map[string]interface{}{
|
||||
"role": p.roleName,
|
||||
"certificate": string(certBytes),
|
||||
"cf_instance_cert": string(certBytes),
|
||||
"signing_time": signingTime.Format(signatures.TimeFormat),
|
||||
"signature": signature,
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ func TestPCFEndToEnd(t *testing.T) {
|
|||
|
||||
// Configure a CA certificate like a Vault operator would in setting up PCF.
|
||||
if _, err := client.Logical().Write("auth/pcf/config", map[string]interface{}{
|
||||
"certificates": testPCFCerts.CACertificate,
|
||||
"identity_ca_certificates": testPCFCerts.CACertificate,
|
||||
"pcf_api_addr": mockPCFAPI.URL,
|
||||
"pcf_username": pcfAPI.AuthUsername,
|
||||
"pcf_password": pcfAPI.AuthPassword,
|
||||
|
|
2
go.mod
2
go.mod
|
@ -74,7 +74,7 @@ require (
|
|||
github.com/hashicorp/vault-plugin-auth-gcp v0.5.1
|
||||
github.com/hashicorp/vault-plugin-auth-jwt v0.5.1
|
||||
github.com/hashicorp/vault-plugin-auth-kubernetes v0.5.1
|
||||
github.com/hashicorp/vault-plugin-auth-pcf v0.0.0-20190605234735-619218abcd26
|
||||
github.com/hashicorp/vault-plugin-auth-pcf v0.0.0-20190619165123-fb996be2877c
|
||||
github.com/hashicorp/vault-plugin-secrets-ad v0.5.1
|
||||
github.com/hashicorp/vault-plugin-secrets-alicloud v0.5.1
|
||||
github.com/hashicorp/vault-plugin-secrets-azure v0.5.1
|
||||
|
|
2
go.sum
2
go.sum
|
@ -290,6 +290,8 @@ github.com/hashicorp/vault-plugin-auth-pcf v0.0.0-20190524170107-2d769dfedad4 h1
|
|||
github.com/hashicorp/vault-plugin-auth-pcf v0.0.0-20190524170107-2d769dfedad4/go.mod h1:9866PkjxPBXclbEJBKzVGY60pgVIY9b7qZJ5Fa+p6VY=
|
||||
github.com/hashicorp/vault-plugin-auth-pcf v0.0.0-20190605234735-619218abcd26 h1:mz5YaAFveImGMooFLAW14kdSBH4jVdRnKTQYAz0fEHw=
|
||||
github.com/hashicorp/vault-plugin-auth-pcf v0.0.0-20190605234735-619218abcd26/go.mod h1:9866PkjxPBXclbEJBKzVGY60pgVIY9b7qZJ5Fa+p6VY=
|
||||
github.com/hashicorp/vault-plugin-auth-pcf v0.0.0-20190619165123-fb996be2877c h1:/g4Yr7pCTfKVqjUUVO4/Pkd3Vmw2TB3znuB4lF7ZNNY=
|
||||
github.com/hashicorp/vault-plugin-auth-pcf v0.0.0-20190619165123-fb996be2877c/go.mod h1:AjWJZO3nIHzc1inkx57x5qtIIcpi1sejXiwJVcNpjyc=
|
||||
github.com/hashicorp/vault-plugin-secrets-ad v0.5.1 h1:BdiASUZLOvOUs317EnaUNjGxTSw0PYGQA7zJZhDKLC4=
|
||||
github.com/hashicorp/vault-plugin-secrets-ad v0.5.1/go.mod h1:EH9CI8+0aWRBz8eIgGth0QjttmHWlGvn+8ZmX/ZUetE=
|
||||
github.com/hashicorp/vault-plugin-secrets-alicloud v0.5.1 h1:72K91p4uLhT/jgtBq2zV5Wn8ocvny4sAN56XOcTxK1w=
|
||||
|
|
|
@ -20,6 +20,3 @@ cmd/verify/verify
|
|||
|
||||
pkg*
|
||||
bin*
|
||||
|
||||
# Ignore fake certificates generated for tests
|
||||
testdata/fake-certificates*
|
||||
|
|
|
@ -22,7 +22,7 @@ testshort: fmtcheck generate
|
|||
CGO_ENABLED=0 VAULT_TOKEN= VAULT_ACC= go test -short -tags='$(BUILD_TAGS)' $(TEST) $(TESTARGS) -count=1 -timeout=20m -parallel=4
|
||||
|
||||
# test runs the unit tests and vets the code
|
||||
test: gencerts fmtcheck generate
|
||||
test: fmtcheck generate
|
||||
CGO_ENABLED=0 VAULT_TOKEN= VAULT_ACC= go test ./... -v -tags='$(BUILD_TAGS)' $(TEST) $(TESTARGS) -count=1 -timeout=20m -parallel=4
|
||||
|
||||
testcompile: fmtcheck generate
|
||||
|
@ -45,9 +45,6 @@ bootstrap:
|
|||
fmtcheck:
|
||||
@sh -c "'$(CURDIR)/scripts/gofmtcheck.sh'"
|
||||
|
||||
gencerts:
|
||||
@sh -c "'$(CURDIR)/scripts/generate-test-certs.sh'"
|
||||
|
||||
fmt:
|
||||
gofmt -w $(GOFMT_FILES)
|
||||
|
||||
|
|
|
@ -3,17 +3,270 @@
|
|||
This plugin leverages PCF's [App and Container Identity Assurance](https://content.pivotal.io/blog/new-in-pcf-2-1-app-container-identity-assurance-via-automatic-cert-rotation)
|
||||
for authenticating to Vault.
|
||||
|
||||
## Known Risks
|
||||
|
||||
This authentication engine uses PCF's instance identity service to authenticate users to Vault. Because PCF
|
||||
makes its CA certificate and **private key** available to certain users at any time, it's possible for someone
|
||||
with access to them to self-issue identity certificates that meet the criteria for a Vault role, allowing
|
||||
them to gain unintended access to Vault.
|
||||
|
||||
For this reason, we recommend that if you choose this auth method, you **carefully guard access to
|
||||
the private key** for your instance identity CA certificate. In CredHub, it can be obtained through the
|
||||
following call: `$ credhub get -n /cf/diego-instance-identity-root-ca`.
|
||||
|
||||
Take extra steps to limit access to that path in CredHub, whether it be through use of CredHub's ACL system,
|
||||
or through carefully limiting the users who can access CredHub.
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Obtaining Your Instance Identity CA Certificate
|
||||
|
||||
In most versions of PCF, instance identity is enabled out-of-the-box. Check by pulling your CA certificate,
|
||||
which you'll need to configure this auth engine. There are undoubtedly multiple ways to do this, but this
|
||||
is how we did it.
|
||||
|
||||
#### From CF Dev
|
||||
|
||||
```
|
||||
$ bosh int --path /diego_instance_identity_ca ~/.cfdev/state/bosh/creds.yml
|
||||
```
|
||||
|
||||
#### From CredHub
|
||||
|
||||
[Install and authenticate to the PCF command line tool](https://docs.pivotal.io/tiledev/2-2/pcf-command.html),
|
||||
and [install jq](https://stedolan.github.io/jq/).
|
||||
|
||||
Get the credentials you'll use for CredHub:
|
||||
```
|
||||
$ pcf settings | jq '.products[0].director_credhub_client_credentials'
|
||||
```
|
||||
|
||||
SSH into your Ops Manager VM:
|
||||
```
|
||||
ssh -i ops_mgr.pem ubuntu@$OPS_MGR_URL
|
||||
```
|
||||
Please note that the above `OPS_MGR_URL` shouldn't be prepended with `https://`.
|
||||
|
||||
Log in to Credhub using the credentials you obtained earlier:
|
||||
```
|
||||
$ credhub login --client-name=director_to_credhub --client-secret=CoJPkrsYi3c-Fx2QHEEDyaEEUuOfYMzw
|
||||
```
|
||||
6. Retrieve the CA information:
|
||||
```
|
||||
$ credhub get -n /cf/diego-instance-identity-root-ca
|
||||
```
|
||||
7. You'll receive a response like:
|
||||
```
|
||||
id: be2bd996-1d35-443b-b81c-90095024d5e7
|
||||
name: /cf/diego-instance-identity-root-ca
|
||||
type: certificate
|
||||
value:
|
||||
ca: |
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDNDCCAhygAwIBAgITPqTy1qvfHNEVuxsl9l1glY85OTANBgkqhkiG9w0BAQsF
|
||||
ADAqMSgwJgYDVQQDEx9EaWVnbyBJbnN0YW5jZSBJZGVudGl0eSBSb290IENBMB4X
|
||||
DTE5MDYwNjA5MTIwMVoXDTIyMDYwNTA5MTIwMVowKjEoMCYGA1UEAxMfRGllZ28g
|
||||
SW5zdGFuY2UgSWRlbnRpdHkgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP
|
||||
ADCCAQoCggEBALa8xGDYT/q3UzEKAsLDajhuHxPpIPFlCXwp6u8U5Qrf427Xof7n
|
||||
rXRKzRu3g7E20U/OwzgBi3VZs8T29JGNWeA2k0HtX8oQ+Wc8Qngz9M8t1h9SZlx5
|
||||
fGfxPt3x7xozaIGJ8p4HKQH1ZlirL7dzun7Y+7m6Ey8cMVsepqUs64r8+KpCbxKJ
|
||||
rV04qtTNlr0LG3yOxSHlip+DDvUVL3jSFz/JDWxwCymiFBAh0QjG1LKp2FisURoX
|
||||
GY+HJbf2StpK3i4dYnxQXQlMDpipozK7WFxv3gH4Q6YMZvlmIPidAF8FxfDIsYcq
|
||||
TgQ5q0pr9mbu8oKbZ74vyZMqiy+r9vLhbu0CAwEAAaNTMFEwHQYDVR0OBBYEFAHf
|
||||
pwqBhZ8/A6ZAvU+p5JPz/omjMB8GA1UdIwQYMBaAFAHfpwqBhZ8/A6ZAvU+p5JPz
|
||||
/omjMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBADuDJev+6bOC
|
||||
v7t9SS4Nd/zeREuF9IKsHDHrYUZBIO1aBQbOO1iDtL4VA3LBEx6fOgN5fbxroUsz
|
||||
X9/6PtxLe+5U8i5MOztK+OxxPrtDfnblXVb6IW4EKhTnWesS7R2WnOWtzqRQXKFU
|
||||
voBn3QckLV1o9eqzYIE/aob4z0GaVanA9PSzzbVPsX79RCD1B7NmV0cKEQ7IrCrh
|
||||
L7ElDV/GlNrtVdHjY0mwz9iu+0YJvxvcHDTERi106b28KXzJz+P5/hyg2wqRXzdI
|
||||
faXAjW0kuq5nxyJUALwxD/8pz77uNt4w6WfJoSDM6XrAIhh15K3tZg9EzBmAZ/5D
|
||||
jK0RcmCyaXw=
|
||||
-----END CERTIFICATE-----
|
||||
certificate: |
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDNDCCAhygAwIBAgITPqTy1qvfHNEVuxsl9l1glY85OTANBgkqhkiG9w0BAQsF
|
||||
ADAqMSgwJgYDVQQDEx9EaWVnbyBJbnN0YW5jZSBJZGVudGl0eSBSb290IENBMB4X
|
||||
DTE5MDYwNjA5MTIwMVoXDTIyMDYwNTA5MTIwMVowKjEoMCYGA1UEAxMfRGllZ28g
|
||||
SW5zdGFuY2UgSWRlbnRpdHkgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP
|
||||
ADCCAQoCggEBALa8xGDYT/q3UzEKAsLDajhuHxPpIPFlCXwp6u8U5Qrf427Xof7n
|
||||
rXRKzRu3g7E20U/OwzgBi3VZs8T29JGNWeA2k0HtX8oQ+Wc8Qngz9M8t1h9SZlx5
|
||||
fGfxPt3x7xozaIGJ8p4HKQH1ZlirL7dzun7Y+7m6Ey8cMVsepqUs64r8+KpCbxKJ
|
||||
rV04qtTNlr0LG3yOxSHlip+DDvUVL3jSFz/JDWxwCymiFBAh0QjG1LKp2FisURoX
|
||||
GY+HJbf2StpK3i4dYnxQXQlMDpipozK7WFxv3gH4Q6YMZvlmIPidAF8FxfDIsYcq
|
||||
TgQ5q0pr9mbu8oKbZ74vyZMqiy+r9vLhbu0CAwEAAaNTMFEwHQYDVR0OBBYEFAHf
|
||||
pwqBhZ8/A6ZAvU+p5JPz/omjMB8GA1UdIwQYMBaAFAHfpwqBhZ8/A6ZAvU+p5JPz
|
||||
/omjMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBADuDJev+6bOC
|
||||
v7t9SS4Nd/zeREuF9IKsHDHrYUZBIO1aBQbOO1iDtL4VA3LBEx6fOgN5fbxroUsz
|
||||
X9/6PtxLe+5U8i5MOztK+OxxPrtDfnblXVb6IW4EKhTnWesS7R2WnOWtzqRQXKFU
|
||||
voBn3QckLV1o9eqzYIE/aob4z0GaVanA9PSzzbVPsX79RCD1B7NmV0cKEQ7IrCrh
|
||||
L7ElDV/GlNrtVdHjY0mwz9iu+0YJvxvcHDTERi106b28KXzJz+P5/hyg2wqRXzdI
|
||||
faXAjW0kuq5nxyJUALwxD/8pz77uNt4w6WfJoSDM6XrAIhh15K3tZg9EzBmAZ/5D
|
||||
jK0RcmCyaXw=
|
||||
-----END CERTIFICATE-----
|
||||
private_key: |
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEAtrzEYNhP+rdTMQoCwsNqOG4fE+kg8WUJfCnq7xTlCt/jbteh
|
||||
/uetdErNG7eDsTbRT87DOAGLdVmzxPb0kY1Z4DaTQe1fyhD5ZzxCeDP0zy3WH1Jm
|
||||
XHl8Z/E+3fHvGjNogYnyngcpAfVmWKsvt3O6ftj7uboTLxwxWx6mpSzrivz4qkJv
|
||||
EomtXTiq1M2WvQsbfI7FIeWKn4MO9RUveNIXP8kNbHALKaIUECHRCMbUsqnYWKxR
|
||||
GhcZj4clt/ZK2kreLh1ifFBdCUwOmKmjMrtYXG/eAfhDpgxm+WYg+J0AXwXF8Mix
|
||||
hypOBDmrSmv2Zu7ygptnvi/JkyqLL6v28uFu7QIDAQABAoIBACGIPhjvWK3PGirz
|
||||
hVIr/b/hJT7IFs11Fup73qqEkQsPznI2i3l1FfUzDLQ7VqUcRAh7DoOmdOrRzRUl
|
||||
o/dZktZ77UW5w0wXFU0GV8Qq9I9X/+S7gCEUAeoo8LpVfOS37kNnBuhMtA+x8lfv
|
||||
AdCOIfjI5FhOdtq8N6pa04WX2pkkRzQkIpneRcLPqq1WwKZK1o8zxCnbP+4SI8yo
|
||||
dTB2ldDY+1vusMYoFch2IsPDMCxVUpAYOoO0jvyOM4cqm0m7P+Tb6Qy19GG50ZfC
|
||||
PlEK76YTOurdirGWdnGC+JEf4smrGgIvJGKEb55/qbqPow/o6Bf4XVXnOHaboW/W
|
||||
Mu4cGFkCgYEA2JaQxFF51yuz4W7/VFZCCix3woPH12fZ3fz7aVMUfn4WQwMbNLVa
|
||||
7G4gdRclReOs5FLM7jPqiTxDckXnoWRc5Ff9Xn4c2ioXrqzXr5V4qJ4GAWxAx0uM
|
||||
w1u5ZpVL2HO12pat74MnYw7EJ65oznQNFSC1FAGn5BJ9f5HFk4X3ngMCgYEA1/1Q
|
||||
XmAk1XJUQ0RP0EehwNipZQPhodGmrqHBGED+N/8+eRMo4shi//EcPXZ222j9kqAE
|
||||
inPA9qaDxhBjgMt+JBFkj/bmTO/Yz8XusBBa5YlN9Ev30zlO+dRlM41/piluPTzf
|
||||
vNQuzyNIzl2Gzd71R1TcuFWIDxn8BR0/cBA/5E8CgYAT7m8uEc1jlrr8AOnwSevT
|
||||
4dm3hccLNJxhCFnejG2zYkkMK6oCRLo0TcIg5Ftivhv3+wKu3Qo1TN1sE7DIMmM2
|
||||
BD7lxjdDgGIjifZjSx8KbVhiIyMm8/XlOHisTwrmxWcz0W/6PZiPThmRCUTN0vIt
|
||||
QpBHYgugOm9gIPsMo2RxHwKBgQDOUDjZvUrR3GCi1HjMwe+/bvX3+MopMULfYsE4
|
||||
srRittxs+KFAZxsx0ZUhHKySDurQiSttOP6kXBBZPERfvYFjYH3HipcX/K8EYNQL
|
||||
t8OrqAkfhwVV7VMEDx8QLGQ3SzHzKteo3qFL2S9teCcRNZzjoysmpQTPMAnstLBp
|
||||
EgyFvwKBgQDObNn/Kmfwi6TuGhIjLtBuUEhp5n4EUtysTUZs/15h02MWOfI8CCvm
|
||||
xWb6/vZrVggxGlZgZtKy9+COPVpEMFaVdwq9uq4lW77sSBwGIwfzHd1CIjce6mSg
|
||||
P5+wO3aTgvr4n8D5NyWcnYPJKRQzqWHHnfk+9TQA1l0g3/yQXfCx2A==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
version_created_at: "2019-06-06T09:12:01Z"
|
||||
```
|
||||
|
||||
From that response, copy the first certificate (under `ca: |`) and place
|
||||
it into its own separate file using a plain text editor like [Sublime](https://www.sublimetext.com/).
|
||||
The following instructions assume you name the file `ca.crt`.
|
||||
Remove any tabs before each line, and any trailing space or lines. When
|
||||
complete, your CA certificate should look like this:
|
||||
|
||||
```
|
||||
$ cat ca.crt
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDNDCCAhygAwIBAgITPqTy1qvfHNEVuxsl9l1glY85OTANBgkqhkiG9w0BAQsF
|
||||
ADAqMSgwJgYDVQQDEx9EaWVnbyBJbnN0YW5jZSBJZGVudGl0eSBSb290IENBMB4X
|
||||
DTE5MDYwNjA5MTIwMVoXDTIyMDYwNTA5MTIwMVowKjEoMCYGA1UEAxMfRGllZ28g
|
||||
SW5zdGFuY2UgSWRlbnRpdHkgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP
|
||||
ADCCAQoCggEBALa8xGDYT/q3UzEKAsLDajhuHxPpIPFlCXwp6u8U5Qrf427Xof7n
|
||||
rXRKzRu3g7E20U/OwzgBi3VZs8T29JGNWeA2k0HtX8oQ+Wc8Qngz9M8t1h9SZlx5
|
||||
fGfxPt3x7xozaIGJ8p4HKQH1ZlirL7dzun7Y+7m6Ey8cMVsepqUs64r8+KpCbxKJ
|
||||
rV04qtTNlr0LG3yOxSHlip+DDvUVL3jSFz/JDWxwCymiFBAh0QjG1LKp2FisURoX
|
||||
GY+HJbf2StpK3i4dYnxQXQlMDpipozK7WFxv3gH4Q6YMZvlmIPidAF8FxfDIsYcq
|
||||
TgQ5q0pr9mbu8oKbZ74vyZMqiy+r9vLhbu0CAwEAAaNTMFEwHQYDVR0OBBYEFAHf
|
||||
pwqBhZ8/A6ZAvU+p5JPz/omjMB8GA1UdIwQYMBaAFAHfpwqBhZ8/A6ZAvU+p5JPz
|
||||
/omjMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBADuDJev+6bOC
|
||||
v7t9SS4Nd/zeREuF9IKsHDHrYUZBIO1aBQbOO1iDtL4VA3LBEx6fOgN5fbxroUsz
|
||||
X9/6PtxLe+5U8i5MOztK+OxxPrtDfnblXVb6IW4EKhTnWesS7R2WnOWtzqRQXKFU
|
||||
voBn3QckLV1o9eqzYIE/aob4z0GaVanA9PSzzbVPsX79RCD1B7NmV0cKEQ7IrCrh
|
||||
L7ElDV/GlNrtVdHjY0mwz9iu+0YJvxvcHDTERi106b28KXzJz+P5/hyg2wqRXzdI
|
||||
faXAjW0kuq5nxyJUALwxD/8pz77uNt4w6WfJoSDM6XrAIhh15K3tZg9EzBmAZ/5D
|
||||
jK0RcmCyaXw=
|
||||
-----END CERTIFICATE-----
|
||||
```
|
||||
|
||||
Verify that this certificate can be properly parsed like so:
|
||||
|
||||
```
|
||||
$ openssl x509 -in ca.crt -text -noout
|
||||
Certificate:
|
||||
Data:
|
||||
Version: 3 (0x2)
|
||||
Serial Number:
|
||||
3e:a4:f2:d6:ab:df:1c:d1:15:bb:1b:25:f6:5d:60:95:8f:39:39
|
||||
Signature Algorithm: sha256WithRSAEncryption
|
||||
Issuer: CN=Diego Instance Identity Root CA
|
||||
Validity
|
||||
Not Before: Jun 6 09:12:01 2019 GMT
|
||||
Not After : Jun 5 09:12:01 2022 GMT
|
||||
Subject: CN=Diego Instance Identity Root CA
|
||||
Subject Public Key Info:
|
||||
Public Key Algorithm: rsaEncryption
|
||||
Public-Key: (2048 bit)
|
||||
Modulus:
|
||||
00:b6:bc:c4:60:d8:4f:fa:b7:53:31:0a:02:c2:c3:
|
||||
6a:38:6e:1f:13:e9:20:f1:65:09:7c:29:ea:ef:14:
|
||||
e5:0a:df:e3:6e:d7:a1:fe:e7:ad:74:4a:cd:1b:b7:
|
||||
83:b1:36:d1:4f:ce:c3:38:01:8b:75:59:b3:c4:f6:
|
||||
f4:91:8d:59:e0:36:93:41:ed:5f:ca:10:f9:67:3c:
|
||||
42:78:33:f4:cf:2d:d6:1f:52:66:5c:79:7c:67:f1:
|
||||
3e:dd:f1:ef:1a:33:68:81:89:f2:9e:07:29:01:f5:
|
||||
66:58:ab:2f:b7:73:ba:7e:d8:fb:b9:ba:13:2f:1c:
|
||||
31:5b:1e:a6:a5:2c:eb:8a:fc:f8:aa:42:6f:12:89:
|
||||
ad:5d:38:aa:d4:cd:96:bd:0b:1b:7c:8e:c5:21:e5:
|
||||
8a:9f:83:0e:f5:15:2f:78:d2:17:3f:c9:0d:6c:70:
|
||||
0b:29:a2:14:10:21:d1:08:c6:d4:b2:a9:d8:58:ac:
|
||||
51:1a:17:19:8f:87:25:b7:f6:4a:da:4a:de:2e:1d:
|
||||
62:7c:50:5d:09:4c:0e:98:a9:a3:32:bb:58:5c:6f:
|
||||
de:01:f8:43:a6:0c:66:f9:66:20:f8:9d:00:5f:05:
|
||||
c5:f0:c8:b1:87:2a:4e:04:39:ab:4a:6b:f6:66:ee:
|
||||
f2:82:9b:67:be:2f:c9:93:2a:8b:2f:ab:f6:f2:e1:
|
||||
6e:ed
|
||||
Exponent: 65537 (0x10001)
|
||||
X509v3 extensions:
|
||||
X509v3 Subject Key Identifier:
|
||||
01:DF:A7:0A:81:85:9F:3F:03:A6:40:BD:4F:A9:E4:93:F3:FE:89:A3
|
||||
X509v3 Authority Key Identifier:
|
||||
keyid:01:DF:A7:0A:81:85:9F:3F:03:A6:40:BD:4F:A9:E4:93:F3:FE:89:A3
|
||||
|
||||
X509v3 Basic Constraints: critical
|
||||
CA:TRUE
|
||||
Signature Algorithm: sha256WithRSAEncryption
|
||||
3b:83:25:eb:fe:e9:b3:82:bf:bb:7d:49:2e:0d:77:fc:de:44:
|
||||
4b:85:f4:82:ac:1c:31:eb:61:46:41:20:ed:5a:05:06:ce:3b:
|
||||
58:83:b4:be:15:03:72:c1:13:1e:9f:3a:03:79:7d:bc:6b:a1:
|
||||
4b:33:5f:df:fa:3e:dc:4b:7b:ee:54:f2:2e:4c:3b:3b:4a:f8:
|
||||
ec:71:3e:bb:43:7e:76:e5:5d:56:fa:21:6e:04:2a:14:e7:59:
|
||||
eb:12:ed:1d:96:9c:e5:ad:ce:a4:50:5c:a1:54:be:80:67:dd:
|
||||
07:24:2d:5d:68:f5:ea:b3:60:81:3f:6a:86:f8:cf:41:9a:55:
|
||||
a9:c0:f4:f4:b3:cd:b5:4f:b1:7e:fd:44:20:f5:07:b3:66:57:
|
||||
47:0a:11:0e:c8:ac:2a:e1:2f:b1:25:0d:5f:c6:94:da:ed:55:
|
||||
d1:e3:63:49:b0:cf:d8:ae:fb:46:09:bf:1b:dc:1c:34:c4:46:
|
||||
2d:74:e9:bd:bc:29:7c:c9:cf:e3:f9:fe:1c:a0:db:0a:91:5f:
|
||||
37:48:7d:a5:c0:8d:6d:24:ba:ae:67:c7:22:54:00:bc:31:0f:
|
||||
ff:29:cf:be:ee:36:de:30:e9:67:c9:a1:20:cc:e9:7a:c0:22:
|
||||
18:75:e4:ad:ed:66:0f:44:cc:19:80:67:fe:43:8c:ad:11:72:
|
||||
60:b2:69:7c
|
||||
```
|
||||
|
||||
Congratulations! You have obtained the CA certificate you'll use for configuring
|
||||
this auth engine.
|
||||
|
||||
### Obtaining Your API Credentials
|
||||
|
||||
From the directory where you added `metadata` in the previous step to authenticate to the pcf command-line
|
||||
tool, run the following commands:
|
||||
|
||||
```
|
||||
$ pcf target
|
||||
$ cf api
|
||||
```
|
||||
|
||||
The api endpoint given will be used for configuring this Vault auth method.
|
||||
|
||||
This plugin was tested with Org Manager level permissions, but lower level permissions may be usable.
|
||||
```
|
||||
$ cf create-user vault pa55word
|
||||
$ cf orgs
|
||||
$ cf org-users my-example-org
|
||||
$ cf set-org-role Alice my-example-org OrgManager
|
||||
```
|
||||
|
||||
Since the PCF API tends to use a self-signed certificate, you'll also need to configure
|
||||
Vault to trust that certificate. You can obtain its API certificate via:
|
||||
|
||||
```
|
||||
openssl s_client -showcerts -servername domain.com -connect domain.com:443
|
||||
```
|
||||
|
||||
You'll see a certificate outputted as part of the response, which should be broken
|
||||
out into a separate well-formatted file like the `ca.crt` above, and used for the
|
||||
`pcf_api_trusted_certificates` field.
|
||||
|
||||
## Downloading the Plugin
|
||||
|
||||
- `$ git clone git@github.com:hashicorp/vault-plugin-auth-pcf.git`
|
||||
- `$ cd vault-plugin-auth-pcf`
|
||||
- `$ PCF_HOME=$(pwd)`
|
||||
- `$ make test`
|
||||
- `$ make tools`
|
||||
|
||||
`$ make test` is run above to generate valid fake certificates in your `testdata/fake-certificates` folder.
|
||||
`$ make tools` is run above to install a number of tools that have been placed here in the `cmd` directory
|
||||
to make your life easier. Running the command will place them in your `$GOPATH/bin` directory.
|
||||
|
||||
## Sample Usage
|
||||
|
||||
|
@ -21,22 +274,21 @@ Please note that this example uses `generate-signature`, a tool installed throug
|
|||
|
||||
First, enable the PCF auth engine.
|
||||
```
|
||||
$ vault auth enable vault-plugin-auth-pcf
|
||||
$ vault auth enable pcf
|
||||
```
|
||||
|
||||
Next, configure the plugin. In the `config` call below, the `certificates` configured is intended to be the CA
|
||||
certificate that has been configured as the `diego.executor.instance_identity_ca_cert` in your environment. For
|
||||
instructions on configuring this, see PCF's
|
||||
[Enabling Instance Identity](https://docs.cloudfoundry.org/adminguide/instance-identity.html).
|
||||
Next, configure the plugin. In the `config` call below, `certificates` is intended to be the instance
|
||||
identity CA certificate you pulled above.
|
||||
|
||||
In the CF Dev environment the default API address is `https://api.dev.cfdev.sh`. The default username and password
|
||||
are `admin`, `admin`. In a production environment, these attributes will vary.
|
||||
```
|
||||
$ vault write auth/vault-plugin-auth-pcf/config \
|
||||
certificates=@$PCF_HOME/testdata/fake-certificates/ca.crt \
|
||||
pcf_api_addr=http://127.0.0.1:33671 \
|
||||
pcf_username=username \
|
||||
pcf_password=password
|
||||
$ vault write auth/pcf/config \
|
||||
certificates=@ca.crt \
|
||||
pcf_api_addr=https://api.dev.cfdev.sh \
|
||||
pcf_username=admin \
|
||||
pcf_password=admin \
|
||||
pcf_api_trusted_certificates=@pcfapi.crt
|
||||
```
|
||||
|
||||
Then, add a role that will be used to grant specific Vault policies to those logging in with it. When a constraint like
|
||||
|
@ -47,15 +299,11 @@ configuring as many bound parameters as possible.
|
|||
Also, by default, the IP address on the certificate presented at login must match that of the caller. However, if
|
||||
your callers tend to be proxied, this may not work for you. If that's the case, set `disable_ip_matching` to true.
|
||||
```
|
||||
$ vault write auth/vault-plugin-auth-pcf/roles/test-role \
|
||||
$ vault write auth/pcf/roles/test-role \
|
||||
bound_application_ids=2d3e834a-3a25-4591-974c-fa5626d5d0a1 \
|
||||
bound_space_ids=3d2eba6b-ef19-44d5-91dd-1975b0db5cc9 \
|
||||
bound_organization_ids=34a878d0-c2f9-4521-ba73-a9f664e82c7bf \
|
||||
bound_instance_ids=1bf2e7f6-2d1d-41ec-501c-c70 \
|
||||
policies=foo-policies \
|
||||
ttl=86400s \
|
||||
max_ttl=86400s \
|
||||
period=86400s
|
||||
policies=foo-policies
|
||||
```
|
||||
|
||||
Logging in is intended to be performed using your `CF_INSTANCE_CERT` and `CF_INSTANCE_KEY`. This is an example of how
|
||||
|
@ -63,13 +311,7 @@ it can be done.
|
|||
```
|
||||
$ export CF_INSTANCE_CERT=$PCF_HOME/testdata/fake-certificates/instance.crt
|
||||
$ export CF_INSTANCE_KEY=$PCF_HOME/testdata/fake-certificates/instance.key
|
||||
$ export SIGNING_TIME=$(date -u)
|
||||
$ export ROLE='test-role'
|
||||
$ vault write auth/vault-plugin-auth-pcf/login \
|
||||
role=$ROLE \
|
||||
certificate=@$CF_INSTANCE_CERT \
|
||||
signing-time="$SIGNING_TIME" \
|
||||
signature=$(generate-signature)
|
||||
$ vault login -method=pcf role=test-role
|
||||
```
|
||||
|
||||
### Updating the CA Certificate
|
||||
|
@ -94,6 +336,21 @@ login will succeed.
|
|||
|
||||
## Troubleshooting
|
||||
|
||||
### Obtaining a Certificate Error from the PCF API
|
||||
|
||||
When configuring this plugin, you may encounter an error like:
|
||||
```
|
||||
Error writing data to auth/pcf/config: Error making API request.
|
||||
|
||||
URL: PUT http://127.0.0.1:8200/v1/auth/pcf/config
|
||||
Code: 500. Errors:
|
||||
|
||||
* 1 error occurred:
|
||||
* unable to establish an initial connection to the PCF API: Could not get api /v2/info: Get https://api.sys.lagunaniguel.cf-app.com/v2/info: x509: certificate signed by unknown authority
|
||||
```
|
||||
|
||||
To resolve this error, review instructions above regarding setting the `pcf_api_trusted_certificates` field.
|
||||
|
||||
### verify-certs
|
||||
|
||||
This tool, installed by `make tools`, is for verifying that your CA certificate, client certificate, and client
|
||||
|
@ -103,9 +360,7 @@ debugging authentication problems that may be related to your certificates, it's
|
|||
```
|
||||
verify-certs -ca-cert=local/path/to/ca.crt -instance-cert=local/path/to/instance.crt -instance-key=local/path/to/instance.key
|
||||
```
|
||||
The `ca-cert` should be the cert that was used to issue the given client certificate. In the CF Dev environment,
|
||||
it can be obtained via `$ bosh int --path /diego_instance_identity_ca ~/.cfdev/state/bosh/creds.yml`. In a prod
|
||||
environment, it should be available through the Ops Manager API.
|
||||
The `ca-cert` should be the cert that was used to issue the given client certificate.
|
||||
|
||||
The `instance-cert` given should be the value for the `CF_INSTANCE_CERT` variable in the PCF environment you're
|
||||
using, and the `instance-key` should be the value for the `CF_INSTANCE_KEY`.
|
||||
|
|
|
@ -2,7 +2,7 @@ package pcf
|
|||
|
||||
import (
|
||||
"context"
|
||||
"github.com/hashicorp/go-hclog"
|
||||
|
||||
"github.com/hashicorp/vault/sdk/framework"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
)
|
||||
|
@ -16,9 +16,7 @@ const (
|
|||
)
|
||||
|
||||
func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) {
|
||||
b := &backend{
|
||||
logger: hclog.Default(),
|
||||
}
|
||||
b := &backend{}
|
||||
b.Backend = &framework.Backend{
|
||||
AuthRenew: b.pathLoginRenew,
|
||||
Help: backendHelp,
|
||||
|
@ -42,7 +40,6 @@ func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend,
|
|||
|
||||
type backend struct {
|
||||
*framework.Backend
|
||||
logger hclog.Logger
|
||||
}
|
||||
|
||||
const backendHelp = `
|
||||
|
|
|
@ -45,13 +45,13 @@ func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (*api.Secret, erro
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certificate := string(certBytes)
|
||||
cfInstanceCertContents := string(certBytes)
|
||||
|
||||
signingTime := time.Now().UTC()
|
||||
signatureData := &signatures.SignatureData{
|
||||
SigningTime: signingTime,
|
||||
Role: role,
|
||||
Certificate: certificate,
|
||||
CFInstanceCertContents: cfInstanceCertContents,
|
||||
}
|
||||
signature, err := signatures.Sign(pathToInstanceKey, signatureData)
|
||||
if err != nil {
|
||||
|
@ -60,7 +60,7 @@ func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (*api.Secret, erro
|
|||
|
||||
loginData := map[string]interface{}{
|
||||
"role": role,
|
||||
"certificate": certificate,
|
||||
"cf_instance_cert": cfInstanceCertContents,
|
||||
"signing_time": signingTime.Format(signatures.TimeFormat),
|
||||
"signature": signature,
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ go 1.12
|
|||
|
||||
require (
|
||||
github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1
|
||||
github.com/hashicorp/go-hclog v0.9.2
|
||||
github.com/hashicorp/go-multierror v1.0.0
|
||||
github.com/hashicorp/go-sockaddr v1.0.2
|
||||
|
|
|
@ -1,35 +1,14 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
import "time"
|
||||
|
||||
// NewConfiguration is the way a Configuration is intended to be obtained. It ensures the
|
||||
// given certificates are valid and prepares a CA certificate pool to be used for client
|
||||
// certificate verification.
|
||||
func NewConfiguration(certificates []string, pcfAPIAddr, pcfUsername, pcfPassword string) (*Configuration, error) {
|
||||
config := &Configuration{
|
||||
Certificates: certificates,
|
||||
PCFAPIAddr: pcfAPIAddr,
|
||||
PCFUsername: pcfUsername,
|
||||
PCFPassword: pcfPassword,
|
||||
}
|
||||
pool := x509.NewCertPool()
|
||||
for _, certificate := range certificates {
|
||||
if ok := pool.AppendCertsFromPEM([]byte(certificate)); !ok {
|
||||
return nil, fmt.Errorf("couldn't append CA certificate: %s", certificate)
|
||||
}
|
||||
}
|
||||
config.verifyOpts = &x509.VerifyOptions{Roots: pool}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// Configuration is not intended to by directly instantiated; please use NewConfiguration.
|
||||
// Configuration is the config as it's reflected in Vault's storage system.
|
||||
type Configuration struct {
|
||||
// Certificates are the CA certificates that should be used for verifying client certificates.
|
||||
Certificates []string `json:"certificates"`
|
||||
// IdentityCACertificates are the CA certificates that should be used for verifying client certificates.
|
||||
IdentityCACertificates []string `json:"identity_ca_certificates"`
|
||||
|
||||
// IdentityCACertificates that, if presented by the PCF API, should be trusted.
|
||||
PCFAPICertificates []string `json:"pcf_api_trusted_certificates"`
|
||||
|
||||
// PCFAPIAddr is the address of PCF's API, ex: "https://api.dev.cfdev.sh" or "http://127.0.0.1:33671"
|
||||
PCFAPIAddr string `json:"pcf_api_addr"`
|
||||
|
@ -40,17 +19,11 @@ type Configuration struct {
|
|||
// The password for the PCF API.
|
||||
PCFPassword string `json:"pcf_password"`
|
||||
|
||||
// verifyOpts is intentionally lower-cased so it won't be stored in JSON.
|
||||
// Instead, this struct is expected to be created from NewConfiguration
|
||||
// so that it'll populate this field.
|
||||
verifyOpts *x509.VerifyOptions
|
||||
}
|
||||
// The maximum seconds old a login request's signing time can be.
|
||||
// This is configurable because in some test environments we found as much as 2 hours of clock drift.
|
||||
LoginMaxSecOld time.Duration `json:"login_max_seconds_old"`
|
||||
|
||||
// VerifyOpts returns the options that can be used for verifying client certificates,
|
||||
// including the CA certificate pool.
|
||||
func (c *Configuration) VerifyOpts() (x509.VerifyOptions, error) {
|
||||
if c.verifyOpts == nil {
|
||||
return x509.VerifyOptions{}, errors.New("verify options are unset")
|
||||
}
|
||||
return *c.verifyOpts, nil
|
||||
// The maximum seconds ahead a login request's signing time can be.
|
||||
// This is configurable because in some test environments we found as much as 2 hours of clock drift.
|
||||
LoginMaxSecAhead time.Duration `json:"login_max_seconds_ahead"`
|
||||
}
|
||||
|
|
|
@ -4,9 +4,10 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cloudfoundry-community/go-cfclient"
|
||||
"github.com/hashicorp/vault-plugin-auth-pcf/models"
|
||||
"github.com/hashicorp/vault-plugin-auth-pcf/util"
|
||||
"github.com/hashicorp/vault/sdk/framework"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
)
|
||||
|
@ -17,10 +18,18 @@ func (b *backend) pathConfig() *framework.Path {
|
|||
return &framework.Path{
|
||||
Pattern: "config",
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"certificates": {
|
||||
"identity_ca_certificates": {
|
||||
Required: true,
|
||||
Type: framework.TypeStringSlice,
|
||||
Description: "The PEM-format CA certificates.",
|
||||
DisplayName: "Identity CA Certificates",
|
||||
DisplayValue: `-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----`,
|
||||
Description: "The PEM-format CA certificates that are required to have issued the instance certificates presented for logging in.",
|
||||
},
|
||||
"pcf_api_trusted_certificates": {
|
||||
Type: framework.TypeStringSlice,
|
||||
DisplayName: "PCF API Trusted IdentityCACertificates",
|
||||
DisplayValue: `-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----`,
|
||||
Description: "The PEM-format CA certificates that are acceptable for the PCF API to present.",
|
||||
},
|
||||
"pcf_api_addr": {
|
||||
Required: true,
|
||||
|
@ -44,6 +53,22 @@ func (b *backend) pathConfig() *framework.Path {
|
|||
Description: "The password for PCF’s API.",
|
||||
DisplaySensitive: true,
|
||||
},
|
||||
"login_max_seconds_old": {
|
||||
Type: framework.TypeDurationSecond,
|
||||
DisplayName: "Login Max Seconds Old",
|
||||
DisplayValue: "300",
|
||||
Description: `Duration in seconds for the maximum acceptable age of a "signing_time". Useful for clock drift.
|
||||
Set low to reduce the opportunity for replay attacks.`,
|
||||
Default: 300,
|
||||
},
|
||||
"login_max_seconds_ahead": {
|
||||
Type: framework.TypeInt,
|
||||
DisplayName: "Login Max Seconds Ahead",
|
||||
DisplayValue: "60",
|
||||
Description: `Duration in seconds for the maximum acceptable length in the future a "signing_time" can be. Useful for clock drift.
|
||||
Set low to reduce the opportunity for replay attacks.`,
|
||||
Default: 60,
|
||||
},
|
||||
},
|
||||
Operations: map[logical.Operation]framework.OperationHandler{
|
||||
logical.CreateOperation: &framework.PathOperation{
|
||||
|
@ -71,9 +96,9 @@ func (b *backend) operationConfigCreateUpdate(ctx context.Context, req *logical.
|
|||
}
|
||||
if config == nil {
|
||||
// They're creating a config.
|
||||
certificates := data.Get("certificates").([]string)
|
||||
if len(certificates) == 0 {
|
||||
return logical.ErrorResponse("'certificates' is required"), nil
|
||||
identityCACerts := data.Get("identity_ca_certificates").([]string)
|
||||
if len(identityCACerts) == 0 {
|
||||
return logical.ErrorResponse("'identity_ca_certificates' is required"), nil
|
||||
}
|
||||
pcfApiAddr := data.Get("pcf_api_addr").(string)
|
||||
if pcfApiAddr == "" {
|
||||
|
@ -87,27 +112,35 @@ func (b *backend) operationConfigCreateUpdate(ctx context.Context, req *logical.
|
|||
if pcfPassword == "" {
|
||||
return logical.ErrorResponse("'pcf_password' is required"), nil
|
||||
}
|
||||
config, err = models.NewConfiguration(certificates, pcfApiAddr, pcfUsername, pcfPassword)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
pcfApiCertificates := data.Get("pcf_api_trusted_certificates").([]string)
|
||||
|
||||
// Default this to 5 minutes.
|
||||
loginMaxSecOld := 300 * time.Second
|
||||
if raw, ok := data.GetOk("login_max_seconds_old"); ok {
|
||||
loginMaxSecOld = time.Duration(raw.(int)) * time.Second
|
||||
}
|
||||
|
||||
// Default this to 1 minute.
|
||||
loginMaxSecAhead := 60 * time.Second
|
||||
if raw, ok := data.GetOk("login_max_seconds_ahead"); ok {
|
||||
loginMaxSecAhead = time.Duration(raw.(int)) * time.Second
|
||||
}
|
||||
config = &models.Configuration{
|
||||
IdentityCACertificates: identityCACerts,
|
||||
PCFAPICertificates: pcfApiCertificates,
|
||||
PCFAPIAddr: pcfApiAddr,
|
||||
PCFUsername: pcfUsername,
|
||||
PCFPassword: pcfPassword,
|
||||
LoginMaxSecOld: loginMaxSecOld,
|
||||
LoginMaxSecAhead: loginMaxSecAhead,
|
||||
}
|
||||
} else {
|
||||
// They're updating a config. Only update the fields that have been sent in the call.
|
||||
if raw, ok := data.GetOk("certificates"); ok {
|
||||
switch v := raw.(type) {
|
||||
case []interface{}:
|
||||
certificates := make([]string, len(v))
|
||||
for _, certificateIfc := range v {
|
||||
certificate, ok := certificateIfc.(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
certificates = append(certificates, certificate)
|
||||
}
|
||||
config.Certificates = certificates
|
||||
case string:
|
||||
config.Certificates = []string{v}
|
||||
if raw, ok := data.GetOk("identity_ca_certificates"); ok {
|
||||
config.IdentityCACertificates = raw.([]string)
|
||||
}
|
||||
if raw, ok := data.GetOk("pcf_api_trusted_certificates"); ok {
|
||||
config.PCFAPICertificates = raw.([]string)
|
||||
}
|
||||
if raw, ok := data.GetOk("pcf_api_addr"); ok {
|
||||
config.PCFAPIAddr = raw.(string)
|
||||
|
@ -118,17 +151,19 @@ func (b *backend) operationConfigCreateUpdate(ctx context.Context, req *logical.
|
|||
if raw, ok := data.GetOk("pcf_password"); ok {
|
||||
config.PCFPassword = raw.(string)
|
||||
}
|
||||
if raw, ok := data.GetOk("login_max_seconds_old"); ok {
|
||||
config.LoginMaxSecOld = time.Duration(raw.(int)) * time.Second
|
||||
}
|
||||
if raw, ok := data.GetOk("login_max_seconds_ahead"); ok {
|
||||
config.LoginMaxSecAhead = time.Duration(raw.(int)) * time.Second
|
||||
}
|
||||
}
|
||||
|
||||
// To give early and explicit feedback, make sure the config works by executing a test call
|
||||
// and checking that the API version is supported. If they don't have API v2 running, we would
|
||||
// probably expect a timeout of some sort below because it's first called in the NewClient
|
||||
// probably expect a timeout of some sort below because it's first called in the NewPCFClient
|
||||
// method.
|
||||
client, err := cfclient.NewClient(&cfclient.Config{
|
||||
ApiAddress: config.PCFAPIAddr,
|
||||
Username: config.PCFUsername,
|
||||
Password: config.PCFPassword,
|
||||
})
|
||||
client, err := util.NewPCFClient(config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to establish an initial connection to the PCF API: %s", err)
|
||||
}
|
||||
|
@ -160,9 +195,12 @@ func (b *backend) operationConfigRead(ctx context.Context, req *logical.Request,
|
|||
}
|
||||
return &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"certificates": config.Certificates,
|
||||
"identity_ca_certificates": config.IdentityCACertificates,
|
||||
"pcf_api_trusted_certificates": config.PCFAPICertificates,
|
||||
"pcf_api_addr": config.PCFAPIAddr,
|
||||
"pcf_username": config.PCFUsername,
|
||||
"login_max_seconds_old": config.LoginMaxSecOld / time.Second,
|
||||
"login_max_seconds_ahead": config.LoginMaxSecAhead / time.Second,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
@ -183,22 +221,8 @@ func config(ctx context.Context, storage logical.Storage) (*models.Configuration
|
|||
if entry == nil {
|
||||
return nil, nil
|
||||
}
|
||||
configMap := make(map[string]interface{})
|
||||
if err := entry.DecodeJSON(&configMap); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var certificates []string
|
||||
certificatesIfc := configMap["certificates"].([]interface{})
|
||||
for _, certificateIfc := range certificatesIfc {
|
||||
certificates = append(certificates, certificateIfc.(string))
|
||||
}
|
||||
config, err := models.NewConfiguration(
|
||||
certificates,
|
||||
configMap["pcf_api_addr"].(string),
|
||||
configMap["pcf_username"].(string),
|
||||
configMap["pcf_password"].(string),
|
||||
)
|
||||
if err != nil {
|
||||
config := &models.Configuration{}
|
||||
if err := entry.DecodeJSON(config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return config, nil
|
||||
|
|
|
@ -3,17 +3,16 @@ package pcf
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/hashicorp/vault/sdk/helper/strutil"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cloudfoundry-community/go-cfclient"
|
||||
"github.com/hashicorp/vault-plugin-auth-pcf/models"
|
||||
"github.com/hashicorp/vault-plugin-auth-pcf/signatures"
|
||||
"github.com/hashicorp/vault-plugin-auth-pcf/util"
|
||||
"github.com/hashicorp/vault/sdk/framework"
|
||||
"github.com/hashicorp/vault/sdk/helper/cidrutil"
|
||||
"github.com/hashicorp/vault/sdk/helper/strutil"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
@ -29,11 +28,11 @@ func (b *backend) pathLogin() *framework.Path {
|
|||
DisplayValue: "internally-defined-role",
|
||||
Description: "The name of the role to authenticate against.",
|
||||
},
|
||||
"certificate": {
|
||||
"cf_instance_cert": {
|
||||
Required: true,
|
||||
Type: framework.TypeString,
|
||||
DisplayName: "Client Certificate",
|
||||
Description: "The full client certificate available at the CF_INSTANCE_CERT path on the PCF instance.",
|
||||
DisplayName: "CF_INSTANCE_CERT Contents",
|
||||
Description: "The full body of the file available at the CF_INSTANCE_CERT path on the PCF instance.",
|
||||
},
|
||||
"signing_time": {
|
||||
Required: true,
|
||||
|
@ -60,8 +59,8 @@ func (b *backend) pathLogin() *framework.Path {
|
|||
}
|
||||
|
||||
// operationLoginUpdate is called by those wanting to gain access to Vault.
|
||||
// They present a client certificate that should have been issued by the pre-configured
|
||||
// Certificate Authority, and a signature that should have been signed by the client cert's
|
||||
// They present the instance certificates that should have been issued by the pre-configured
|
||||
// Certificate Authority, and a signature that should have been signed by the instance cert's
|
||||
// private key. If this holds true, there are additional checks verifying everything looks
|
||||
// good before authentication is given.
|
||||
func (b *backend) operationLoginUpdate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
|
@ -78,9 +77,9 @@ func (b *backend) operationLoginUpdate(ctx context.Context, req *logical.Request
|
|||
return logical.ErrorResponse("'signature' is required"), nil
|
||||
}
|
||||
|
||||
clientCertificate := data.Get("certificate").(string)
|
||||
if clientCertificate == "" {
|
||||
return logical.ErrorResponse("'certificate' is required"), nil
|
||||
cfInstanceCertContents := data.Get("cf_instance_cert").(string)
|
||||
if cfInstanceCertContents == "" {
|
||||
return logical.ErrorResponse("'cf_instance_cert' is required"), nil
|
||||
}
|
||||
|
||||
signingTimeRaw := data.Get("signing_time").(string)
|
||||
|
@ -92,31 +91,6 @@ func (b *backend) operationLoginUpdate(ctx context.Context, req *logical.Request
|
|||
return logical.ErrorResponse(err.Error()), nil
|
||||
}
|
||||
|
||||
// Ensure the time it was signed is no more than 5 minutes in the past
|
||||
// or 30 seconds in the future. This is a guard against some replay attacks.
|
||||
fiveMinutesAgo := timeReceived.Add(time.Minute * time.Duration(-5))
|
||||
thirtySecondsFromNow := timeReceived.Add(time.Second * time.Duration(30))
|
||||
if signingTime.Before(fiveMinutesAgo) {
|
||||
return logical.ErrorResponse(fmt.Sprintf("request is too old; signed at %s but received request at %s; raw signing time is %s", signingTime, timeReceived, signingTimeRaw)), nil
|
||||
}
|
||||
if signingTime.After(thirtySecondsFromNow) {
|
||||
return logical.ErrorResponse(fmt.Sprintf("request is too far in the future; signed at %s but received request at %s; raw signing time is %s", signingTime, timeReceived, signingTimeRaw)), nil
|
||||
}
|
||||
|
||||
// Ensure the private key used to create the signature matches our client
|
||||
// certificate, and that it signed the same data as is presented in the body.
|
||||
// This offers some protection against MITM attacks.
|
||||
matchingCert, err := signatures.Verify(signature, &signatures.SignatureData{
|
||||
SigningTime: signingTime,
|
||||
Role: roleName,
|
||||
Certificate: clientCertificate,
|
||||
})
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
}
|
||||
|
||||
// Ensure the matching certificate was actually issued by the CA configured.
|
||||
// This protects against self-generated client certificates.
|
||||
config, err := config(ctx, req.Storage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -124,20 +98,51 @@ func (b *backend) operationLoginUpdate(ctx context.Context, req *logical.Request
|
|||
if config == nil {
|
||||
return nil, errors.New("no CA is configured for verifying client certificates")
|
||||
}
|
||||
verifyOpts, err := config.VerifyOpts()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
// Ensure the time it was signed isn't too far in the past or future.
|
||||
oldestAllowableSigningTime := timeReceived.Add(-1 * config.LoginMaxSecOld)
|
||||
furthestFutureAllowableSigningTime := timeReceived.Add(config.LoginMaxSecAhead)
|
||||
if signingTime.Before(oldestAllowableSigningTime) {
|
||||
return logical.ErrorResponse(fmt.Sprintf("request is too old; signed at %s but received request at %s; allowable seconds old is %d", signingTime, timeReceived, config.LoginMaxSecOld/time.Second)), nil
|
||||
}
|
||||
if _, err := matchingCert.Verify(verifyOpts); err != nil {
|
||||
if signingTime.After(furthestFutureAllowableSigningTime) {
|
||||
return logical.ErrorResponse(fmt.Sprintf("request is too far in the future; signed at %s but received request at %s; allowable seconds in the future is %d", signingTime, timeReceived, config.LoginMaxSecAhead/time.Second)), nil
|
||||
}
|
||||
|
||||
intermediateCert, identityCert, err := util.ExtractCertificates(cfInstanceCertContents)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
}
|
||||
|
||||
// Ensure the private key used to create the signature matches our identity
|
||||
// certificate, and that it signed the same data as is presented in the body.
|
||||
// This offers some protection against MITM attacks.
|
||||
signingCert, err := signatures.Verify(signature, &signatures.SignatureData{
|
||||
SigningTime: signingTime,
|
||||
Role: roleName,
|
||||
CFInstanceCertContents: cfInstanceCertContents,
|
||||
})
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
}
|
||||
// Make sure the identity/signing cert was actually issued by our CA.
|
||||
if err := util.Validate(config.IdentityCACertificates, intermediateCert, identityCert, signingCert); err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
}
|
||||
|
||||
// Read PCF's identity fields from the certificate.
|
||||
pcfCert, err := models.NewPCFCertificateFromx509(matchingCert)
|
||||
pcfCert, err := models.NewPCFCertificateFromx509(signingCert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// It may help some users to be able to easily view the incoming certificate information
|
||||
// in an un-encoded format, as opposed to the encoded format that will appear in the Vault
|
||||
// audit logs.
|
||||
if b.Logger().IsDebug() {
|
||||
b.Logger().Debug(fmt.Sprintf("handling login attempt from %+v", pcfCert))
|
||||
}
|
||||
|
||||
// Ensure the pcf certificate meets the role's constraints.
|
||||
role, err := getRole(ctx, req.Storage, roleName)
|
||||
if err != nil {
|
||||
|
@ -228,13 +233,7 @@ func (b *backend) pathLoginRenew(ctx context.Context, req *logical.Request, data
|
|||
}
|
||||
|
||||
// Reconstruct the certificate and ensure it still meets all constraints.
|
||||
pcfCert, err := models.NewPCFCertificate(
|
||||
instanceID,
|
||||
orgID,
|
||||
spaceID,
|
||||
appID,
|
||||
ipAddr,
|
||||
)
|
||||
pcfCert, err := models.NewPCFCertificate(instanceID, orgID, spaceID, appID, ipAddr)
|
||||
if err := b.validate(config, role, pcfCert, req.Connection.RemoteAddr); err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
}
|
||||
|
@ -269,26 +268,13 @@ func (b *backend) validate(config *models.Configuration, role *models.RoleEntry,
|
|||
}
|
||||
|
||||
// Use the PCF API to ensure everything still exists and to verify whatever we can.
|
||||
client, err := cfclient.NewClient(&cfclient.Config{
|
||||
ApiAddress: config.PCFAPIAddr,
|
||||
Username: config.PCFUsername,
|
||||
Password: config.PCFPassword,
|
||||
})
|
||||
client, err := util.NewPCFClient(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check everything we can using the instance ID.
|
||||
serviceInstance, err := client.GetServiceInstanceByGuid(pcfCert.InstanceID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if serviceInstance.Guid != pcfCert.InstanceID {
|
||||
return fmt.Errorf("cert instance ID %s doesn't match API's expected one of %s", pcfCert.InstanceID, serviceInstance.Guid)
|
||||
}
|
||||
if serviceInstance.SpaceGuid != pcfCert.SpaceID {
|
||||
return fmt.Errorf("cert space ID %s doesn't match API's expected one of %s", pcfCert.SpaceID, serviceInstance.SpaceGuid)
|
||||
}
|
||||
// Here, if it were possible, we _would_ do an API call to check the instance ID,
|
||||
// but currently there's no known way to do that via the pcf API.
|
||||
|
||||
// Check everything we can using the app ID.
|
||||
app, err := client.AppByGuid(pcfCert.AppID)
|
||||
|
|
|
@ -21,7 +21,13 @@ const TimeFormat = "2006-01-02T15:04:05Z"
|
|||
type SignatureData struct {
|
||||
SigningTime time.Time
|
||||
Role string
|
||||
Certificate string
|
||||
|
||||
// CFInstanceCertContents are the full contents/body of the file
|
||||
// available at CF_INSTANCE_CERT. When viewed visually, this file
|
||||
// will contain two certificates. Generally, the first one is the
|
||||
// identity certificate itself, and the second one is the intermediate
|
||||
// certificate that issued it.
|
||||
CFInstanceCertContents string
|
||||
}
|
||||
|
||||
func (s *SignatureData) hash() []byte {
|
||||
|
@ -31,7 +37,7 @@ func (s *SignatureData) hash() []byte {
|
|||
|
||||
func (s *SignatureData) toSign() string {
|
||||
toHash := ""
|
||||
for _, field := range []string{s.SigningTime.UTC().Format(TimeFormat), s.Certificate, s.Role} {
|
||||
for _, field := range []string{s.SigningTime.UTC().Format(TimeFormat), s.CFInstanceCertContents, s.Role} {
|
||||
toHash += field
|
||||
}
|
||||
return toHash
|
||||
|
@ -62,13 +68,11 @@ func Sign(pathToPrivateKey string, signatureData *SignatureData) (string, error)
|
|||
return base64.URLEncoding.EncodeToString(signatureBytes), nil
|
||||
}
|
||||
|
||||
// Verify ensures that a given signature was created by one of the private keys
|
||||
// matching one of the given client certificates. It is possible for a client
|
||||
// certificate string given by PCF to contain multiple certificates within its
|
||||
// body, hence the looping. The matching certificate is returned and should be
|
||||
// further checked to ensure it contains the app, space, and org ID, and CN;
|
||||
// otherwise it would be possible to match against an injected client certificate
|
||||
// to gain authentication.
|
||||
// Verify ensures that a given signature was created by a private key
|
||||
// matching one of the given instance certificates. It returns the matching
|
||||
// certificate, which should further be verified to be the identity certificate,
|
||||
// and to be issued by a chain leading to the root CA certificate. There's a
|
||||
// util function for this named Validate.
|
||||
func Verify(signature string, signatureData *SignatureData) (*x509.Certificate, error) {
|
||||
if signatureData == nil {
|
||||
return nil, errors.New("signatureData must be provided")
|
||||
|
@ -80,58 +84,35 @@ func Verify(signature string, signatureData *SignatureData) (*x509.Certificate,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
certBytes := []byte(signatureData.Certificate)
|
||||
cfInstanceCertContentsBytes := []byte(signatureData.CFInstanceCertContents)
|
||||
var block *pem.Block
|
||||
var result error
|
||||
for {
|
||||
block, certBytes = pem.Decode(certBytes)
|
||||
block, cfInstanceCertContentsBytes = pem.Decode(cfInstanceCertContentsBytes)
|
||||
if block == nil {
|
||||
break
|
||||
}
|
||||
clientCerts, err := x509.ParseCertificates(block.Bytes)
|
||||
instanceCerts, err := x509.ParseCertificates(block.Bytes)
|
||||
if err != nil {
|
||||
result = multierror.Append(result, err)
|
||||
continue
|
||||
}
|
||||
for _, clientCert := range clientCerts {
|
||||
publicKey, ok := clientCert.PublicKey.(*rsa.PublicKey)
|
||||
for _, instanceCert := range instanceCerts {
|
||||
publicKey, ok := instanceCert.PublicKey.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
result = multierror.Append(result, fmt.Errorf("not an rsa public key, it's a %t", clientCert.PublicKey))
|
||||
result = multierror.Append(result, fmt.Errorf("not an rsa public key, it's a %t", instanceCert.PublicKey))
|
||||
continue
|
||||
}
|
||||
|
||||
if err := rsa.VerifyPSS(publicKey, crypto.SHA256, signatureData.hash(), signatureBytes, nil); err != nil {
|
||||
result = multierror.Append(result, err)
|
||||
continue
|
||||
}
|
||||
// Success
|
||||
return clientCert, nil
|
||||
return instanceCert, nil
|
||||
}
|
||||
}
|
||||
if result == nil {
|
||||
return nil, fmt.Errorf("no matching client certificate found for %s in %s", signature, signatureData.Certificate)
|
||||
return nil, fmt.Errorf("no matching certificate found for %s in %s", signature, signatureData.CFInstanceCertContents)
|
||||
}
|
||||
return nil, result
|
||||
}
|
||||
|
||||
func IsIssuer(pathToCACert string, clientCert *x509.Certificate) (bool, error) {
|
||||
caCertBytes, err := ioutil.ReadFile(pathToCACert)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
pool := x509.NewCertPool()
|
||||
if ok := pool.AppendCertsFromPEM(caCertBytes); !ok {
|
||||
return false, errors.New("couldn't append CA certificates")
|
||||
}
|
||||
|
||||
verifyOpts := x509.VerifyOptions{
|
||||
Roots: pool,
|
||||
}
|
||||
|
||||
if _, err := clientCert.Verify(verifyOpts); err != nil {
|
||||
return false, err
|
||||
}
|
||||
// Success
|
||||
return true, nil
|
||||
}
|
||||
|
|
99
vendor/github.com/hashicorp/vault-plugin-auth-pcf/testing/certificates/generate.go
generated
vendored
99
vendor/github.com/hashicorp/vault-plugin-auth-pcf/testing/certificates/generate.go
generated
vendored
|
@ -106,10 +106,29 @@ func (e *TestCertificates) Close() error {
|
|||
}
|
||||
|
||||
func generate(instanceID, orgID, spaceID, appID, ipAddress string) (caCert, instanceCert, instanceKey string, err error) {
|
||||
caPrivateKey, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||
caCert, caPriv, err := generateCA("", nil)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
intermediateCert, intermediatePriv, err := generateCA(caCert, caPriv)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
identityCert, identityPriv, err := generateIdentity(intermediateCert, intermediatePriv, instanceID, orgID, spaceID, appID, ipAddress)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
// Convert the identity key to something appropriate for a file body.
|
||||
out := &bytes.Buffer{}
|
||||
pem.Encode(out, pemBlockForKey(identityPriv))
|
||||
instanceKey = out.String()
|
||||
return caCert, fmt.Sprintf("%s%s", intermediateCert, identityCert), instanceKey, nil
|
||||
}
|
||||
|
||||
func generateCA(caCert string, caPriv *rsa.PrivateKey) (string, *rsa.PrivateKey, error) {
|
||||
template := x509.Certificate{
|
||||
SerialNumber: big.NewInt(1),
|
||||
Subject: pkix.Name{
|
||||
|
@ -119,35 +138,66 @@ func generate(instanceID, orgID, spaceID, appID, ipAddress string) (caCert, inst
|
|||
CommonName: "test-CA",
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(time.Hour * 24 * 180),
|
||||
NotAfter: time.Now().Add(time.Hour * 24 * 365 * 100),
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: true,
|
||||
}
|
||||
|
||||
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(caPrivateKey), caPrivateKey)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
out := &bytes.Buffer{}
|
||||
pem.Encode(out, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
|
||||
caCert = out.String()
|
||||
out.Reset()
|
||||
// Default to self-signing certificates by listing using itself as a parent.
|
||||
parent := &template
|
||||
|
||||
// If a cert is provided, use it as the parent.
|
||||
if caCert != "" {
|
||||
block, certBytes := pem.Decode([]byte(caCert))
|
||||
if block == nil {
|
||||
return "", "", "", errors.New("block shouldn't be nil")
|
||||
return "", nil, errors.New("block shouldn't be nil")
|
||||
}
|
||||
if len(certBytes) > 0 {
|
||||
return "", "", "", errors.New("there shouldn't be more bytes")
|
||||
return "", nil, errors.New("there shouldn't be more bytes")
|
||||
}
|
||||
ca509cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
return "", nil, err
|
||||
}
|
||||
parent = ca509cert
|
||||
}
|
||||
// If a private key isn't provided, make a new one.
|
||||
priv := caPriv
|
||||
if priv == nil {
|
||||
newPriv, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
priv = newPriv
|
||||
}
|
||||
|
||||
template = x509.Certificate{
|
||||
derBytes, err := x509.CreateCertificate(rand.Reader, &template, parent, publicKey(priv), priv)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
out := &bytes.Buffer{}
|
||||
pem.Encode(out, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
|
||||
cert := out.String()
|
||||
return cert, priv, nil
|
||||
}
|
||||
|
||||
func generateIdentity(caCert string, caPriv *rsa.PrivateKey, instanceID, orgID, spaceID, appID, ipAddress string) (string, *rsa.PrivateKey, error) {
|
||||
block, certBytes := pem.Decode([]byte(caCert))
|
||||
if block == nil {
|
||||
return "", nil, errors.New("block shouldn't be nil")
|
||||
}
|
||||
if len(certBytes) > 0 {
|
||||
return "", nil, errors.New("there shouldn't be more bytes")
|
||||
}
|
||||
ca509cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
template := x509.Certificate{
|
||||
SerialNumber: big.NewInt(1),
|
||||
Subject: pkix.Name{
|
||||
Country: []string{"US"},
|
||||
|
@ -161,7 +211,7 @@ func generate(instanceID, orgID, spaceID, appID, ipAddress string) (caCert, inst
|
|||
CommonName: instanceID,
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(time.Hour * 24 * 180),
|
||||
NotAfter: time.Now().Add(time.Hour * 24 * 365 * 100),
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
|
@ -169,25 +219,20 @@ func generate(instanceID, orgID, spaceID, appID, ipAddress string) (caCert, inst
|
|||
IPAddresses: []net.IP{net.ParseIP(ipAddress)},
|
||||
}
|
||||
|
||||
clientPrivateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
derBytes, err = x509.CreateCertificate(rand.Reader, &template, ca509cert, publicKey(clientPrivateKey), caPrivateKey)
|
||||
derBytes, err := x509.CreateCertificate(rand.Reader, &template, ca509cert, publicKey(priv), caPriv)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
out := &bytes.Buffer{}
|
||||
pem.Encode(out, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
|
||||
instanceCert = out.String()
|
||||
out.Reset()
|
||||
|
||||
pem.Encode(out, pemBlockForKey(clientPrivateKey))
|
||||
instanceKey = out.String()
|
||||
out.Reset()
|
||||
|
||||
return caCert, instanceCert, instanceKey, nil
|
||||
cert := out.String()
|
||||
return cert, priv, nil
|
||||
}
|
||||
|
||||
func makePathTo(certOrKey string) (string, error) {
|
||||
|
|
79
vendor/github.com/hashicorp/vault-plugin-auth-pcf/util/certificates.go
generated
vendored
Normal file
79
vendor/github.com/hashicorp/vault-plugin-auth-pcf/util/certificates.go
generated
vendored
Normal file
|
@ -0,0 +1,79 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
)
|
||||
|
||||
// ExtractCertificates takes the contents of the file at CF_INSTANCE_CERT, which typically are
|
||||
// comprised of two certificates. One is the identity certificate, and one is an intermediate
|
||||
// CA certificate which is crucial in linking the identity cert back to the configured root
|
||||
// certificate. It splits these two certificates apart, and identifies the certificate marked
|
||||
// as a CA as the intermediate cert, and the one not marked as a CA as the identity certificate.
|
||||
// It may error if the given file contents or certificates aren't as expected.
|
||||
func ExtractCertificates(cfInstanceCertContents string) (intermediateCert, identityCert *x509.Certificate, err error) {
|
||||
certPairBytes := []byte(cfInstanceCertContents)
|
||||
numCerts := 0
|
||||
var block *pem.Block
|
||||
var result error
|
||||
for {
|
||||
block, certPairBytes = pem.Decode(certPairBytes)
|
||||
if block == nil {
|
||||
break
|
||||
}
|
||||
certs, err := x509.ParseCertificates(block.Bytes)
|
||||
if err != nil {
|
||||
result = multierror.Append(result, err)
|
||||
continue
|
||||
}
|
||||
for _, cert := range certs {
|
||||
if cert.IsCA {
|
||||
intermediateCert = cert
|
||||
} else {
|
||||
identityCert = cert
|
||||
}
|
||||
numCerts++
|
||||
}
|
||||
}
|
||||
if numCerts != 2 {
|
||||
result = multierror.Append(fmt.Errorf("expected 2 certs but received %s", certPairBytes))
|
||||
}
|
||||
if intermediateCert == nil {
|
||||
result = multierror.Append(fmt.Errorf("no intermediate certificate found in %s", certPairBytes))
|
||||
}
|
||||
if identityCert == nil {
|
||||
result = multierror.Append(fmt.Errorf("no identity cert found in %s", certPairBytes))
|
||||
}
|
||||
return intermediateCert, identityCert, result
|
||||
}
|
||||
|
||||
// Validate takes a group of trusted CA certificates, an intermediate certificate, an identity certificate,
|
||||
// and a signing certificate, and makes sure they have the following properties:
|
||||
// - The identity certificate is the same as the signing certificate
|
||||
// - The identity certificate chains to at least one trusted CA
|
||||
func Validate(caCerts []string, intermediateCert, identityCert, signingCert *x509.Certificate) error {
|
||||
if !reflect.DeepEqual(identityCert, signingCert) {
|
||||
return errors.New("signature not generated by identity cert")
|
||||
}
|
||||
roots := x509.NewCertPool()
|
||||
for _, caCert := range caCerts {
|
||||
if ok := roots.AppendCertsFromPEM([]byte(caCert)); !ok {
|
||||
return errors.New("couldn't append root certificate")
|
||||
}
|
||||
}
|
||||
intermediates := x509.NewCertPool()
|
||||
intermediates.AddCert(intermediateCert)
|
||||
verifyOpts := x509.VerifyOptions{
|
||||
Roots: roots,
|
||||
Intermediates: intermediates,
|
||||
}
|
||||
if _, err := signingCert.Verify(verifyOpts); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,3 +1,42 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/cloudfoundry-community/go-cfclient"
|
||||
"github.com/hashicorp/go-cleanhttp"
|
||||
"github.com/hashicorp/vault-plugin-auth-pcf/models"
|
||||
)
|
||||
|
||||
const BashTimeFormat = "Mon Jan 2 15:04:05 MST 2006"
|
||||
|
||||
// NewPCFClient does some work that's needed every time we use the PCF client,
|
||||
// namely using cleanhttp and configuring it to match the user conf.
|
||||
func NewPCFClient(config *models.Configuration) (*cfclient.Client, error) {
|
||||
clientConf := &cfclient.Config{
|
||||
ApiAddress: config.PCFAPIAddr,
|
||||
Username: config.PCFUsername,
|
||||
Password: config.PCFPassword,
|
||||
HttpClient: cleanhttp.DefaultClient(),
|
||||
}
|
||||
rootCAs, err := x509.SystemCertPool()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rootCAs == nil {
|
||||
rootCAs = x509.NewCertPool()
|
||||
}
|
||||
for _, certificate := range config.PCFAPICertificates {
|
||||
if ok := rootCAs.AppendCertsFromPEM([]byte(certificate)); !ok {
|
||||
return nil, fmt.Errorf("couldn't append PCF API cert to trust: %s", certificate)
|
||||
}
|
||||
}
|
||||
tlsConfig := &tls.Config{
|
||||
RootCAs: rootCAs,
|
||||
}
|
||||
clientConf.HttpClient.Transport = &http.Transport{TLSClientConfig: tlsConfig}
|
||||
return cfclient.NewClient(clientConf)
|
||||
}
|
||||
|
|
|
@ -335,7 +335,7 @@ github.com/hashicorp/vault-plugin-auth-gcp/plugin/cache
|
|||
github.com/hashicorp/vault-plugin-auth-jwt
|
||||
# github.com/hashicorp/vault-plugin-auth-kubernetes v0.5.1
|
||||
github.com/hashicorp/vault-plugin-auth-kubernetes
|
||||
# github.com/hashicorp/vault-plugin-auth-pcf v0.0.0-20190605234735-619218abcd26
|
||||
# github.com/hashicorp/vault-plugin-auth-pcf v0.0.0-20190619165123-fb996be2877c
|
||||
github.com/hashicorp/vault-plugin-auth-pcf
|
||||
github.com/hashicorp/vault-plugin-auth-pcf/signatures
|
||||
github.com/hashicorp/vault-plugin-auth-pcf/models
|
||||
|
|
Loading…
Reference in New Issue