From 03057bf2f18d05c6086b22adffeea3f053419e7c Mon Sep 17 00:00:00 2001 From: Kumbirai Tanekha Date: Mon, 24 Jul 2023 16:07:31 +0100 Subject: [PATCH] Address open questions + update POC --- .../conjur_csi_provider_poc/README.md | 161 +++++++++++++++- .../conjur_csi_provider_poc/go.mod | 11 +- .../conjur_csi_provider_poc/go.sum | 40 +++- .../conjur_csi_provider_poc/main.go | 180 +++++++++++++++--- .../conjur_csi_provider_solution_design.md | 32 +++- 5 files changed, 385 insertions(+), 39 deletions(-) diff --git a/design/providers/kubernetes_secrets_storage_csi/conjur_csi_provider_poc/README.md b/design/providers/kubernetes_secrets_storage_csi/conjur_csi_provider_poc/README.md index 3e7117ddbf..5b66503c7e 100644 --- a/design/providers/kubernetes_secrets_storage_csi/conjur_csi_provider_poc/README.md +++ b/design/providers/kubernetes_secrets_storage_csi/conjur_csi_provider_poc/README.md @@ -3,6 +3,7 @@ This POC provides a basic implemtation of a provider for Secret Store CSI Driver. Here are the steps install and setup a Kubernetes in Docker cluster + ```sh # Install kind # For AMD64 / x86_64 @@ -35,23 +36,169 @@ helm install secrets-store-csi-driver secrets-store-csi-driver \ # Build container image for conjur-csi-provider docker build -f - -t conjur-csi-provider . < /dev/null 2>&1 > || kubectl exec --namespace conjur \ + deployment/conjur-conjur-oss \ + --container=conjur-oss \ + -- conjurctl account create "default" > created_account +cat ./created_account + +# Run Conjur CLI +kubectl run conjur-cli-pod --image=conjur-cli:latest --image-pull-policy=Never --namespace=conjur --command -- sleep infinity + +# Setup authenticator and secrets using Conjur CLI + +# Create files +mkdir -p ./files +# Create policy +cat << EOL > ./files/policy.yml +--- + +- !host + +- !host + id: kubernetes/applications/system:serviceaccount:default:default + annotations: + authn-jwt/kube/kubernetes.io/namespace: default + +- !host + id: host1 + annotations: + authn-jwt/kube/kubernetes.io/namespace: csi + authn-jwt/kube/kubernetes.io/serviceaccount/name: default + +- !variable secretVar + +- !permit + # Give permissions to the human user to update the secret and fetch the secret. + role: !host /host1 + privileges: [read, update, execute] + resource: !variable secretVar + +# This policy defines a JWT authenticator to be used with Kubernetis cluster +- !policy + id: conjur/authn-jwt/kube + body: + - !webservice + + # Uncomment one of following variables depending on the public availability + # of the Service Account Issuer Discovery service in Kubernetes + # If the service is publicly available, uncomment 'jwks-uri'. + # If the service is not available, uncomment 'public-keys' + + # - !variable + # id: jwks-uri + + - !variable + id: public-keys + + # This variable tells Conjur which claim in the JWT to use to determine the conjur host identity. + # - !variable + # id: token-app-property # Most likely set to "sub" for Kubernetes + + # This variable is used with token-app-property. This variable will hold the conjur policy path that contains the conjur host identity found by looking at the claim entered in token-app-property. + # - !variable + # id: identity-path + + # Uncomment ca-cert if the JWKS website cert isn't trusted by conjur + + # - !variable + # id: ca-cert + + # This variable contains what "iss" in the JWT. + - !variable + id: issuer + + # This variable contains what "aud" is the JWT. + # - !variable + # id: audience + + - !permit + role: !host /kubernetes/applications/system:serviceaccount:default:default + privilege: [ read, authenticate ] + resource: !webservice + + - !permit + role: !host /host1 + privilege: [ read, authenticate ] + resource: !webservice +EOL + +# Get values required by authn-jwt authenticator and store to files +kubectl get --raw /.well-known/openid-configuration | jq -r .issuer > ./files/issuer +echo '{"type": "jwks", "value": '$(kubectl get --raw /openid/v1/jwks)' }' > ./files/jwks + +# Copy files into CLI container +kubectl -n "${CONJUR_NAMESPACE}" cp ./files conjur-cli-pod:/files -c conjur-cli-pod + +# Exec into CLI container +kubectl -n "${CONJUR_NAMESPACE}" exec -it conjur-cli-pod bash + +# Run this script manually +echo " +# Initialise CLI and login +conjur init -u https://conjur-conjur-oss.conjur.svc.cluster.local -a "default" --self-signed +conjur login -i admin + +# Apply policy +conjur policy replace -b root -f ./policy.yml + +# Inspect resources +# conjur list +# conjur resource show default:host:host1 + +# Set secret value +conjur variable set -i secretVar -v something-super-secret + +# Set variable values on authenticator +conjur variable set -i conjur/authn-jwt/kube/public-keys -v $(cat /files/jwks) +conjur variable set -i conjur/authn-jwt/kube/issuer -v $(cat /files/issuer) + +# Validate authenticator +curl -v -k --request POST 'https://conjur-conjur-oss.conjur.svc.cluster.local/authn-jwt/kube/default/host%2Fhost1/authenticate' --header 'Content-Type: application/x-www-form-urlencoded' --header 'Accept-Encoding: base64' --data-urlencode 'jwt='$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) +" + # Remove app and provider # kubectl delete pod --force app conjur-csi-provider diff --git a/design/providers/kubernetes_secrets_storage_csi/conjur_csi_provider_poc/go.mod b/design/providers/kubernetes_secrets_storage_csi/conjur_csi_provider_poc/go.mod index 8a2262c6c5..f26cb1c7dc 100644 --- a/design/providers/kubernetes_secrets_storage_csi/conjur_csi_provider_poc/go.mod +++ b/design/providers/kubernetes_secrets_storage_csi/conjur_csi_provider_poc/go.mod @@ -3,15 +3,24 @@ module conjur_csi_provider_poc go 1.20 require ( + github.com/cyberark/conjur-api-go v0.11.1 google.golang.org/grpc v1.56.0 sigs.k8s.io/secrets-store-csi-driver v1.3.4 ) require ( + github.com/alessio/shellescape v1.4.1 // indirect + github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect + github.com/danieljoos/wincred v1.1.2 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/golang/protobuf v1.5.3 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/sirupsen/logrus v1.8.1 // indirect + github.com/zalando/go-keyring v0.2.3-0.20230503081219-17db2e5354bd // indirect golang.org/x/net v0.9.0 // indirect - golang.org/x/sys v0.7.0 // indirect + golang.org/x/sys v0.8.0 // indirect golang.org/x/text v0.9.0 // indirect google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect google.golang.org/protobuf v1.30.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/design/providers/kubernetes_secrets_storage_csi/conjur_csi_provider_poc/go.sum b/design/providers/kubernetes_secrets_storage_csi/conjur_csi_provider_poc/go.sum index ae06d1db26..470de55875 100644 --- a/design/providers/kubernetes_secrets_storage_csi/conjur_csi_provider_poc/go.sum +++ b/design/providers/kubernetes_secrets_storage_csi/conjur_csi_provider_poc/go.sum @@ -1,12 +1,42 @@ +github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= +github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cyberark/conjur-api-go v0.11.1 h1:vjaMkw0geJsA+ikMM6UDLg4VLFQWKo/B0i9IWlOQ1f0= +github.com/cyberark/conjur-api-go v0.11.1/go.mod h1:n1p46Hj9l8wkZjM17cVYdfcatyPboWyioLGlC0QszCs= +github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0= +github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/zalando/go-keyring v0.2.3-0.20230503081219-17db2e5354bd h1:D+eeEnOlWcMXbwZ5X3oy68nHafBtGcj1jMKFHtVdybY= +github.com/zalando/go-keyring v0.2.3-0.20230503081219-17db2e5354bd/go.mod h1:sI3evg9Wvpw3+n4SqplGSJUMwtDeROfD4nsFz4z9PG0= golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= -golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -18,5 +48,11 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= sigs.k8s.io/secrets-store-csi-driver v1.3.4 h1:rCMOb2I4lJaN6sw0CjT6YHA8ts2yscWAOBGu0EaCIWk= sigs.k8s.io/secrets-store-csi-driver v1.3.4/go.mod h1:jh6wML45aTbxT2YZtU4khzSm8JYxwVrQbhsum+WR6j8= diff --git a/design/providers/kubernetes_secrets_storage_csi/conjur_csi_provider_poc/main.go b/design/providers/kubernetes_secrets_storage_csi/conjur_csi_provider_poc/main.go index 87f6a9471c..23e2175627 100644 --- a/design/providers/kubernetes_secrets_storage_csi/conjur_csi_provider_poc/main.go +++ b/design/providers/kubernetes_secrets_storage_csi/conjur_csi_provider_poc/main.go @@ -1,28 +1,74 @@ package main import ( + "bytes" "context" + "crypto/tls" + "encoding/base64" "encoding/json" + "flag" "fmt" + "io/ioutil" + "log" "net" + "net/http" + "net/url" "os" + "os/signal" + "strings" + "syscall" + "github.com/cyberark/conjur-api-go/conjurapi" "sigs.k8s.io/secrets-store-csi-driver/provider/v1alpha1" "google.golang.org/grpc" ) +const params = ` +{ + "account": "some-org", + "applianceUrl": "https://conjur.some-org.com", + "authnLogin": "path/to/some-policy/some-host", + "authnUrl": "https://conjur-auth.some-org.com", + "cACertPath": "path/to/tls/cert", + "csi.storage.k8s.io/ephemeral": "true", + "csi.storage.k8s.io/pod.name": "app", + "csi.storage.k8s.io/pod.namespace": "csi", + "csi.storage.k8s.io/pod.uid": "89702199-a5f7-4c9b-a8e9-ffe644f7c2e9", + "csi.storage.k8s.io/serviceAccount.name": "default", + "csi.storage.k8s.io/serviceAccount.tokens": "{\"conjur\":{\"token\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6ImpTdDRJbXhEZGJMVTM2LWxnemxzZ3dzRFRVdHUyUTZObndka2FINlo4TDQifQ.eyJhdWQiOlsibm90LXZhdWx0Il0sImV4cCI6MTY4OTcwMDAxMywiaWF0IjoxNjg5Njk2NDEzLCJpc3MiOiJodHRwczovL2t1YmVybmV0ZXMuZGVmYXVsdC5zdmMuY2x1c3Rlci5sb2NhbCIsImt1YmVybmV0ZXMuaW8iOnsibmFtZXNwYWNlIjoiY3NpIiwicG9kIjp7Im5hbWUiOiJhcHAiLCJ1aWQiOiI4OTcwMjE5OS1hNWY3LTRjOWItYThlOS1mZmU2NDRmN2MyZTkifSwic2VydmljZWFjY291bnQiOnsibmFtZSI6ImRlZmF1bHQiLCJ1aWQiOiI4Yjc5YzQ5Zi00NzlmLTRlN2UtYWRhYS1mMGI0YjE0NzkwZGIifX0sIm5iZiI6MTY4OTY5NjQxMywic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmNzaTpkZWZhdWx0In0.rQJgkfKlpMjNVCIKIbU5DjECuK1F5Ma-AgNcreQlFLI5DYR8IwFjKYclV5daN7qOR1Uhi2ejL6CHlUIXa2fHSLr9lhxFiuwGMu1yCh46UaztSb9zUuAurmjAzfj3we07hA3REn9jX5ZK3uvUXMDW6G3n71VYkLEBF8X_cAMgnsChXe_VbPgx879CjjVZuLbBWFxlIueXjqbx3fLUzpTgPGacr3i9g3Q4o_cn3h8XscjkG-CXEmq5FF3I_Lof6MG5rZHWS7IwImEe4y6LI0KkEzETWki1Dg-msPL6gIPI9C7syUD5n_Q5EJQ76Z8vcIXf-8huLoIAo3f46KYXDF-usg\",\"expirationTimestamp\":\"2023-07-18T17:06:53Z\"}}", + "policyPath": "path/to/some-policy/with-secrets", + "secretProviderClass": "database-credentials", + "secrets": "- db_username: \"db/username\" # relative to policyPath, otherwise if it starts with / then it is absolute\n- db_password: \"db/password\"\n" +} +` + +const csiProviderName = "conjur" +const keyParamsCSITokens = "csi.storage.k8s.io/serviceAccount.tokens" + func main() { - s, err := NewConjurCSIProviderServer("/provider/conjur.sock") - if err != nil { - panic(err) - } + socketPath := flag.String("socket", "/provider/conjur.sock", "Path to the socket") + flag.Parse() - err = s.Start() + s, err := NewConjurCSIProviderServer(*socketPath) if err != nil { - panic(err) + log.Fatalf("Failed to create CSI provider server: %v", err) } + go func() { + if err := s.Start(); err != nil { + log.Fatalf("Failed to start CSI provider server: %v", err) + } + }() + + log.Printf("Conjur CSI provider server started. Socket path: %s\n", *socketPath) + + // Wait for termination signals to gracefully stop the server + stop := make(chan os.Signal, 1) + signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM) + <-stop + + s.Stop() } type ConjurCSIProviderServer struct { @@ -31,8 +77,6 @@ type ConjurCSIProviderServer struct { socketPath string returnErr error errorCode string - objects []*v1alpha1.ObjectVersion - files []*v1alpha1.File } // NewConjurCSIProviderServer returns a csi-provider grpc server @@ -41,19 +85,6 @@ func NewConjurCSIProviderServer(socketPath string) (*ConjurCSIProviderServer, er s := &ConjurCSIProviderServer{ grpcServer: server, socketPath: socketPath, - files: []*v1alpha1.File{ - { - Path: "secret1.txt", - Mode: 777, - Contents: []byte("some secret from Conjur"), - }, - }, - objects: []*v1alpha1.ObjectVersion{ - { - Id: "path/to/secret1/in/conjur", - Version: "1", - }, - }, } v1alpha1.RegisterCSIDriverProviderServer(server, s) return s, nil @@ -63,7 +94,7 @@ func (m *ConjurCSIProviderServer) Start() error { var err error m.listener, err = net.Listen("unix", m.socketPath) if err != nil { - return err + return fmt.Errorf("failed to start listener: %w", err) } return m.grpcServer.Serve(m.listener) @@ -79,7 +110,7 @@ func (m *ConjurCSIProviderServer) Mount(ctx context.Context, req *v1alpha1.Mount var filePermission os.FileMode var err error - fmt.Println("request", req) + log.Printf("Mount request: %+v\n", req) if m.returnErr != nil { return &v1alpha1.MountResponse{}, m.returnErr @@ -96,12 +127,42 @@ func (m *ConjurCSIProviderServer) Mount(ctx context.Context, req *v1alpha1.Mount if len(req.GetTargetPath()) == 0 { return nil, fmt.Errorf("missing target path") } + + var tokens map[string]map[string]string + + if err := json.Unmarshal([]byte(attrib[keyParamsCSITokens]), &tokens); err != nil { + log.Fatalf("failed to unmarshal attributes, error: %e", err) + } + + workloadServiceAccountToken := tokens[csiProviderName]["token"] + + testSecretId := "secretVar" + + secretValuesById, err := getConjurSecrets(workloadServiceAccountToken, []string{testSecretId}) + if err != nil { + return &v1alpha1.MountResponse{}, err + } + + files := []*v1alpha1.File{ + { + Path: "secret1.txt", + Mode: 777, + Contents: secretValuesById[testSecretId], + }, + } + objects := []*v1alpha1.ObjectVersion{ + { + Id: testSecretId, + Version: "1", + }, + } + return &v1alpha1.MountResponse{ - ObjectVersion: m.objects, + ObjectVersion: objects, Error: &v1alpha1.Error{ Code: m.errorCode, }, - Files: m.files, + Files: files, }, nil } @@ -109,7 +170,74 @@ func (m *ConjurCSIProviderServer) Mount(ctx context.Context, req *v1alpha1.Mount func (m *ConjurCSIProviderServer) Version(ctx context.Context, req *v1alpha1.VersionRequest) (*v1alpha1.VersionResponse, error) { return &v1alpha1.VersionResponse{ Version: "v1alpha1", - RuntimeName: "conjur", + RuntimeName: csiProviderName, RuntimeVersion: "0.0.1", }, nil } + +func getConjurSecrets(token string, secretIds []string) (map[string][]byte, error) { + // TODO: get the values below from request params + baseURL := "https://conjur-conjur-oss.conjur.svc.cluster.local" + authnId := "authn-jwt/kube" + account := "default" + identity := "host/host1" + + requestUrl, err := url.Parse(baseURL) + if err != nil { + return nil, err + } + + requestUrl = requestUrl.JoinPath(authnId, account, url.PathEscape(identity), "authenticate") + + data := url.Values{} + data.Set("jwt", string(token)) + + transport := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + + client := &http.Client{Transport: transport} + + req, err := http.NewRequest("POST", requestUrl.String(), bytes.NewBufferString(data.Encode())) + if err != nil { + return nil, err + } + + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.Header.Set("Accept-Encoding", "base64") + + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + bodyContents, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + n, err := base64.StdEncoding.Decode(bodyContents, bodyContents) + if err != nil { + return nil, err + } + + bodyContents = bodyContents[:n] + + conjur, err := conjurapi.NewClientFromToken(conjurapi.Config{Account: account, ApplianceURL: baseURL}, string(bodyContents)) + if err != nil { + return nil, err + } + conjur.SetHttpClient(client) + + var secretValuesById = map[string][]byte{} + secretValuesByFullId, err := conjur.RetrieveBatchSecrets(secretIds) + if err != nil { + return nil, err + } + + prefix := fmt.Sprintf("%s:variable:", account) + for k, v := range secretValuesByFullId { + secretValuesById[strings.TrimPrefix(k, prefix)] = v + } + return secretValuesById, nil +} diff --git a/design/providers/kubernetes_secrets_storage_csi/conjur_csi_provider_solution_design.md b/design/providers/kubernetes_secrets_storage_csi/conjur_csi_provider_solution_design.md index 49c9c8271c..144cf33d02 100644 --- a/design/providers/kubernetes_secrets_storage_csi/conjur_csi_provider_solution_design.md +++ b/design/providers/kubernetes_secrets_storage_csi/conjur_csi_provider_solution_design.md @@ -52,6 +52,7 @@ - [Secrets Store CSI Driver](https://github.com/kubernetes-sigs/secrets-store-csi-driver) - [CSI Specification](https://github.com/container-storage-interface/spec) - [Helm Documentation](https://helm.sh/docs/) +- [CSI Driver & Provider Best Practices](https://secrets-store-csi-driver.sigs.k8s.io/topics/best-practices.html) ## Issue description @@ -69,6 +70,8 @@ The following are outside the scope of this design: The proposed solution aims to implement a CyberArk Conjur provider for Kubernetes Secrets Store Container Storage Interface (CSI) driver. This solution allows Kubernetes pods to seamlessly fetch secrets from a Conjur instance using the service account token via the `authn-jwt` authenticator. +Please note that there is a proof of concept accompanying this solution design. This can be found at [./conjur_csi_provider_poc](./conjur_csi_provider_poc). + The core of the CyberArk Conjur provider is a gRPC server that listens on a Unix domain socket. It implements the `CSIDriverProviderServer` interface, which is defined by the Secrets Store CSI Driver project and located in the `sigs.k8s.io/secrets-store-csi-driver/provider/v1alpha1` package. The `CSIDriverProviderServer` interface includes the `Mount` and `Version` methods: @@ -161,7 +164,7 @@ The implementation of the CyberArk Conjur provider for the Kubernetes Secrets St 2. **Create the Helm Charts**: Develop the necessary Kubernetes resource definitions (Deployment, Service, etc.) and values files to manage the deployment of the Conjur provider. This Helm chart will allow users to easily deploy and configure the Conjur provider in their Kubernetes clusters. -3. **Integration with CyberArk Conjur**: The logic for the provider to authenticate with the Conjur API and fetch secrets needs to be developed. This will involve using the `authn-jwt` authenticator for authentication with the workload service account token and making API calls to fetch secrets. +3. **Integration with CyberArk Conjur**: The logic for the provider to authenticate with the Conjur API and fetch secrets needs to be developed. This will involve using the `authn-jwt` authenticator for authentication with the workload service account token and making API calls to fetch secrets. `conjur-api-go` supports the `authn-jwt` authenticator but we need to update it to allow passing in the service account token as an argument, as opposed to environment variable. 4. **Secret File Writing**: The logic for writing the fetched secrets to files at the path specified by the Secrets Store CSI Driver needs to be implemented. @@ -256,7 +259,7 @@ Here are some of the potential nice-to-have features: 3. **Supported Provider Status**: While it's important for the Conjur provider to be functional and reliable, achieving the status of a 'supported provider' in the Kubernetes Secrets Store CSI Driver project would be a significant accomplishment. This would entail meeting certain criteria set by the Kubernetes community, including thorough documentation, robust testing, active maintenance, and more. Achieving this status would give users greater confidence in using the Conjur provider, and it could lead to increased adoption and feedback, helping to drive continuous improvement of the provider. -4. **Support for Other Conjur Authenticators**: Currently, our solution is designed to use the `authn-jwt` authenticator with the workload's service account token. However, it could be beneficial to support other Conjur authentication methods, such as `authn-k8s`. This would provide more flexibility for users with different authentication requirements and could potentially enhance security by allowing more complex authentication schemes. The implementation would involve adding additional logic to the provider to handle the various authenticators and additional configuration options to specify which authenticator to use. +4. ~**Support for Other Conjur Authenticators**: Currently, our solution is designed to use the `authn-jwt` authenticator with the workload's service account token. However, it could be beneficial to support other Conjur authentication methods, such as `authn-k8s`. This would provide more flexibility for users with different authentication requirements and could potentially enhance security by allowing more complex authentication schemes. The implementation would involve adding additional logic to the provider to handle the various authenticators and additional configuration options to specify which authenticator to use.~ ## Backwards compatibility @@ -301,14 +304,37 @@ The documentation will cover: ## Open questions - Does the mount path have to be `/mnt/secrets-store` ? + + Yes - What's the best way to configure things like CA certs for the provider etc. ? Perhaps allowing the `SecretProviderClass` to reference other Kubernetes resources such `ConfigMap` or `Secret` is the answer -- What are our options for supporting authentication via authenticators other than authn-jwt ? + + TBD +- What are our options for supporting authentication via authenticators other than `authn-jwt`` ? + + `authn-jwt` is realistically the only option. The provider runs as an independent process from the workloads. The only information + it has access to for the purposes of authnetication is a Kubernetes service account token for the requesting workload. - What is the upgrade strategy for the CyberArk Conjur provider deployed via Helm? - How to handle secret rotation in CyberArk Conjur? + + Secrets Store CSI Driver has alpha support for rotation, see [documentation](https://secrets-store-csi-driver.sigs.k8s.io/topics/secret-auto-rotation.html) - How should the CyberArk Conjur provider be deployed and managed across multiple Kubernetes clusters? Should there be a separate instance of the provider for each cluster, or can a single instance serve multiple clusters? + + This is not relevant. As per the Secrets Store CSI Driver spec, there will be an instance of the provider on each node. + It will be deployed as a DaemonSet - How should the provider handle errors, such as failure to communicate with the Conjur server or failure to authenticate? What recovery mechanisms should be in place? + + The grpc interface for the provider provides mulitple opportunities to present errors. - How will the provider's operations be monitored and logged? What kind of visibility will administrators have into the provider's activities, and how can potential issues be identified and diagnosed? + + The Secrets Store CSI Driver spec provides multiple breadcrumbs. A good pracice is to emit useful logs and useful error reporting within the provider. - How will the provider perform under heavy load? What are the limitations in terms of the number of secrets it can manage or the rate at which secrets can be fetched? + + We can performance test for this - Will the provider support multiple formats for secrets, such as plain text, JSON, YAML, etc.? How will the desired format be specified? + + To be determined. - How will access to the provider be controlled? What measures will be in place to prevent unauthorized access? + Deploy the providers in a namespace guarded by permissions based access. - Will the provider be compatible with all versions of Kubernetes and Conjur, or are there specific version requirements? + + The provider should match the support of the The Secrets Store CSI Driver spec