From d2cf55b83fe71d41c3a09b35b280d9a48b24088d Mon Sep 17 00:00:00 2001 From: Stuart Clark Date: Tue, 8 Aug 2017 02:20:56 +0100 Subject: Vault AWS EC2 auth (#190) --- test/integration/.gitignore | 2 + test/integration/Dockerfile | 4 +- test/integration/awssvc/main.go | 190 ++++++++++++++++++++++++++++++++ test/integration/datasources_vault.bats | 21 ++++ test/integration/helper.bash | 18 ++- test/integration/metasvc/main.go | 142 ++++++++++++++++++++++++ 6 files changed, 375 insertions(+), 2 deletions(-) create mode 100644 test/integration/awssvc/main.go create mode 100644 test/integration/metasvc/main.go (limited to 'test') diff --git a/test/integration/.gitignore b/test/integration/.gitignore index 9c2b49ab..62c29879 100644 --- a/test/integration/.gitignore +++ b/test/integration/.gitignore @@ -1,2 +1,4 @@ gomplate mirror +meta +aws diff --git a/test/integration/Dockerfile b/test/integration/Dockerfile index e8cbc2ce..894a04a7 100644 --- a/test/integration/Dockerfile +++ b/test/integration/Dockerfile @@ -1,6 +1,6 @@ FROM alpine:edge -ENV VAULT_VER 0.7.0 +ENV VAULT_VER 0.7.3 ENV CONSUL_VER 0.9.0 RUN apk add --no-cache \ curl \ @@ -21,6 +21,8 @@ RUN mkdir /lib64 \ COPY gomplate /bin/gomplate COPY mirror /bin/mirror +COPY meta /bin/meta +COPY aws /bin/aws COPY *.sh /tests/ COPY *.bash /tests/ COPY *.bats /tests/ diff --git a/test/integration/awssvc/main.go b/test/integration/awssvc/main.go new file mode 100644 index 00000000..0a3f8f05 --- /dev/null +++ b/test/integration/awssvc/main.go @@ -0,0 +1,190 @@ +package main + +import ( + "encoding/json" + "flag" + "log" + "net" + "net/http" +) + +// Req - +type Req struct { + Headers http.Header `json:"headers"` +} + +var port string + +func main() { + flag.StringVar(&port, "p", "8082", "Port to listen to") + flag.Parse() + + l, err := net.Listen("tcp", ":"+port) + if err != nil { + log.Fatal(err) + } + // defer l.Close() + http.HandleFunc("/", rootHandler) + + http.HandleFunc("/sts/", stsHandler) + http.HandleFunc("/ec2/", ec2Handler) + http.HandleFunc("/quit", quitHandler(l)) + + http.Serve(l, nil) +} + +func rootHandler(w http.ResponseWriter, r *http.Request) { + req := Req{r.Header} + b, err := json.Marshal(req) + if err != nil { + log.Println(err) + w.WriteHeader(http.StatusBadRequest) + } + w.Header().Set("Content-Type", "application/json") + w.Write(b) +} + +func stsHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/xml") + w.Write([]byte(` + + arn:aws:iam::1:user/Test + AKIAI44QH8DHBEXAMPLE + 1 + + + 01234567-89ab-cdef-0123-456789abcdef + +`)) +} + +func ec2Handler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/xml") + w.Write([]byte(` + 8f7724cf-496f-496e-8fe3-example + + + r-1234567890abcdef0 + 123456789012 + + + + i-00000000000000000 + ami-00000000 + + 16 + running + + ip-192-168-1-88.eu-west-1.compute.internal + ec2-54-194-252-215.eu-west-1.compute.amazonaws.com + + my_keypair + 0 + + t2.micro + 2015-12-22T10:44:05.000Z + + eu-west-1c + + default + + + disabled + + subnet-56f5f633 + vpc-11112222 + 192.168.1.88 + 54.194.252.215 + true + + + sg-e4076980 + SecurityGroup1 + + + x86_64 + ebs + /dev/xvda + + + /dev/xvda + + vol-1234567890abcdef0 + attached + 2015-12-22T10:44:09.000Z + true + + + + hvm + xMcwG14507example + + + Name + Server_1 + + + xen + + + eni-551ba033 + subnet-56f5f633 + vpc-11112222 + Primary network interface + 123456789012 + in-use + 02:dd:2c:5e:01:69 + 192.168.1.88 + ip-192-168-1-88.eu-west-1.compute.internal + true + + + sg-e4076980 + SecurityGroup1 + + + + eni-attach-39697adc + 0 + attached + 2015-12-22T10:44:05.000Z + true + + + 54.194.252.215 + ec2-54-194-252-215.eu-west-1.compute.amazonaws.com + amazon + + + + 192.168.1.88 + ip-192-168-1-88.eu-west-1.compute.internal + true + + 54.194.252.215 + ec2-54-194-252-215.eu-west-1.compute.amazonaws.com + amazon + + + + + + 2001:db8:1234:1a2b::123 + + + + + false + + + + +`)) +} + +func quitHandler(l net.Listener) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + l.Close() + w.WriteHeader(http.StatusNoContent) + } +} diff --git a/test/integration/datasources_vault.bats b/test/integration/datasources_vault.bats index aa6e27f3..90d1a3cc 100644 --- a/test/integration/datasources_vault.bats +++ b/test/integration/datasources_vault.bats @@ -15,9 +15,15 @@ path "*" { } EOF tmpdir=$(mktemp -d) + cp ~/.vault-token ~/.vault-token.bak + start_meta_svc + start_aws_svc } function teardown () { + mv ~/.vault-token.bak ~/.vault-token + stop_meta_svc + stop_aws_svc rm -rf $tmpdir unset VAULT_TOKEN vault delete secret/foo @@ -27,6 +33,7 @@ function teardown () { vault auth-disable approle2 vault auth-disable app-id vault auth-disable app-id2 + vault auth-disable aws vault policy-delete writepol vault policy-delete readpol vault unmount ssh @@ -122,6 +129,20 @@ function teardown () { [[ "${output}" == "$BATS_TEST_DESCRIPTION" ]] } +@test "Testing ec2 vault auth" { + vault write secret/foo value="$BATS_TEST_DESCRIPTION" + vault auth-enable aws + vault write auth/aws/config/client secret_key=secret access_key=access endpoint=http://127.0.0.1:8082/ec2 iam_endpoint=http://127.0.0.1:8082/iam sts_endpoint=http://127.0.0.1:8082/sts + curl -o $tmpdir/certificate -s -f http://127.0.0.1:8081/certificate + vault write auth/aws/config/certificate/testcert type=pkcs7 aws_public_cert=@$tmpdir/certificate + vault write auth/aws/role/ami-00000000 auth_type=ec2 bound_ami_id=ami-00000000 policies=readpol + unset VAULT_TOKEN + rm ~/.vault-token + AWS_META_ENDPOINT=http://127.0.0.1:8081 gomplate -d vault=vault:///secret -i '{{(datasource "vault" "foo").value}}' + [ "$status" -eq 0 ] + [[ "${output}" == "$BATS_TEST_DESCRIPTION" ]] +} + @test "Testing vault auth with dynamic secret" { vault mount ssh vault write ssh/roles/test key_type=otp default_user=user cidr_list=10.0.0.0/8 diff --git a/test/integration/helper.bash b/test/integration/helper.bash index 41c2ac88..00867bc8 100644 --- a/test/integration/helper.bash +++ b/test/integration/helper.bash @@ -34,6 +34,22 @@ function stop_mirror_svc () { wget -q http://127.0.0.1:8080/quit } +function start_meta_svc () { + bin/meta 3>/dev/null & +} + +function stop_meta_svc () { + wget -q http://127.0.0.1:8081/quit +} + +function start_aws_svc () { + bin/aws & +} + +function stop_aws_svc () { + wget -q http://127.0.0.1:8082/quit +} + function wait_for_url () { url=$1 for i in {0..10}; do @@ -55,4 +71,4 @@ function start_consul () { function stop_consul () { PID_FILE=/tmp/gomplate-test-consul.pid kill $(cat $PID_FILE) &>/dev/null -} \ No newline at end of file +} diff --git a/test/integration/metasvc/main.go b/test/integration/metasvc/main.go new file mode 100644 index 00000000..4a1d2626 --- /dev/null +++ b/test/integration/metasvc/main.go @@ -0,0 +1,142 @@ +package main + +import ( + "bytes" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "flag" + "log" + "math/big" + "net" + "net/http" + + "github.com/fullsailor/pkcs7" +) + +var port string +var priv *rsa.PrivateKey +var derBytes []byte + +const instanceDocument = `{ + "devpayProductCodes" : null, + "availabilityZone" : "xx-test-1b", + "privateIp" : "10.1.2.3", + "version" : "2010-08-31", + "instanceId" : "i-00000000000000000", + "billingProducts" : null, + "instanceType" : "t2.micro", + "accountId" : "1", + "imageId" : "ami-00000000", + "pendingTime" : "2000-00-01T0:00:00Z", + "architecture" : "x86_64", + "kernelId" : null, + "ramdiskId" : null, + "region" : "xx-test-1" +}` + +func main() { + flag.StringVar(&port, "p", "8081", "Port to listen to") + flag.Parse() + + certificateGenerate() + + l, err := net.Listen("tcp", ":"+port) + if err != nil { + log.Fatal(err) + } + // defer l.Close() + http.HandleFunc("/", rootHandler) + + http.HandleFunc("/latest/dynamic/instance-identity/pkcs7", pkcsHandler) + http.HandleFunc("/latest/dynamic/instance-identity/document", documentHandler) + http.HandleFunc("/certificate", certificateHandler) + + http.HandleFunc("/quit", quitHandler(l)) + + http.Serve(l, nil) +} + +func certificateGenerate() { + var err error + priv, err = rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + log.Fatalf("failed to generate private key: %s", err) + } + + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + log.Fatalf("failed to generate serial number: %s", err) + } + + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{"Test"}, + }, + } + + derBytes, err = x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) + if err != nil { + log.Fatalf("Failed to create certificate: %s", err) + } +} + +func rootHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.Write([]byte("")) +} + +func pkcsHandler(w http.ResponseWriter, r *http.Request) { + cert, err := x509.ParseCertificate(derBytes) + if err != nil { + log.Fatalf("Cannot decode certificate: %s", err) + } + + // Initialize a SignedData struct with content to be signed + signedData, err := pkcs7.NewSignedData([]byte(instanceDocument)) + if err != nil { + log.Fatalf("Cannot initialize signed data: %s", err) + } + + // Add the signing cert and private key + if err := signedData.AddSigner(cert, priv, pkcs7.SignerInfoConfig{}); err != nil { + log.Fatalf("Cannot add signer: %s", err) + } + + // Finish() to obtain the signature bytes + detachedSignature, err := signedData.Finish() + if err != nil { + log.Fatalf("Cannot finish signing data: %s", err) + } + + encoded := pem.EncodeToMemory(&pem.Block{Type: "PKCS7", Bytes: detachedSignature}) + + encoded = bytes.TrimPrefix(encoded, []byte("-----BEGIN PKCS7-----\n")) + encoded = bytes.TrimSuffix(encoded, []byte("\n-----END PKCS7-----\n")) + + w.Header().Set("Content-Type", "text/plain") + w.Write(encoded) +} + +func documentHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(instanceDocument)) +} + +func certificateHandler(w http.ResponseWriter, r *http.Request) { + encoded := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) + + w.Header().Set("Content-Type", "text/plain") + w.Write(encoded) +} + +func quitHandler(l net.Listener) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + l.Close() + w.WriteHeader(http.StatusNoContent) + } +} -- cgit v1.2.3