Skip to content

Commit

Permalink
[Azure] integrate with pod identity (#28)
Browse files Browse the repository at this point in the history
* support pod identity

* update usepodidentity logic

* fix style

* rm fmt

* test-style

* add docs and csidriver yaml
  • Loading branch information
ritazh committed May 10, 2019
1 parent d78ca48 commit 5be02c5
Show file tree
Hide file tree
Showing 12 changed files with 77 additions and 46 deletions.
2 changes: 1 addition & 1 deletion Makefile
Expand Up @@ -14,7 +14,7 @@

REGISTRY_NAME=ritazh
IMAGE_NAME=secrets-store-csi
IMAGE_VERSION=v0.0.3
IMAGE_VERSION=v0.0.4
IMAGE_TAG=$(REGISTRY_NAME)/$(IMAGE_NAME):$(IMAGE_VERSION)
IMAGE_TAG_LATEST=$(REGISTRY_NAME)/$(IMAGE_NAME):latest
REV=$(shell git describe --long --tags --dirty)
Expand Down
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -152,6 +152,7 @@ kubectl apply -f deploy/rbac-csi-driver-registrar.yaml
kubectl apply -f deploy/rbac-csi-attacher.yaml
kubectl apply -f deploy/csi-secrets-store-attacher.yaml
kubectl apply -f deploy/secrets-store-csi-driver.yaml
kubectl apply -f deploy/csidriver.yaml
```
To validate the installer is running as expected, run the following commands:

Expand Down
2 changes: 1 addition & 1 deletion charts/secrets-store-csi-driver/Chart.yaml
@@ -1,6 +1,6 @@
name: secrets-store-csi-driver
version: 0.0.1
appVersion: 0.0.3
appVersion: 0.0.4
description: A Helm chart to install the SecretsStore CSI Driver inside a Kubernetes cluster.
icon: https://github.com/kubernetes/kubernetes/blob/master/logo/logo.png
sources:
Expand Down
6 changes: 6 additions & 0 deletions charts/secrets-store-csi-driver/templates/csidriver.yaml
@@ -0,0 +1,6 @@
apiVersion: storage.k8s.io/v1beta1
kind: CSIDriver
metadata:
name: secrets-store.csi.k8s.com
spec:
podInfoOnMount: true
2 changes: 1 addition & 1 deletion charts/secrets-store-csi-driver/values.yaml
@@ -1,6 +1,6 @@
image:
repository: ritazh/secrets-store-csi
tag: v0.0.3
tag: v0.0.4
pullPolicy: Always

## `provider` should be one of azure, etc...
Expand Down
6 changes: 6 additions & 0 deletions deploy/csidriver.yaml
@@ -0,0 +1,6 @@
apiVersion: storage.k8s.io/v1beta1
kind: CSIDriver
metadata:
name: secrets-store.csi.k8s.com
spec:
podInfoOnMount: true
2 changes: 1 addition & 1 deletion deploy/secrets-store-csi-driver.yaml
Expand Up @@ -33,7 +33,7 @@ spec:
- mountPath: /registration
name: registration-dir
- name: secrets-store
image: ritazh/secrets-store-csi:v0.0.3
image: ritazh/secrets-store-csi:v0.0.4
args:
- "--v=5"
- "--endpoint=$(CSI_ENDPOINT)"
Expand Down
4 changes: 1 addition & 3 deletions pkg/csi-common/driver.go
Expand Up @@ -17,8 +17,6 @@ limitations under the License.
package csicommon

import (
"fmt"

"github.com/golang/glog"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
Expand Down Expand Up @@ -71,7 +69,7 @@ func (d *CSIDriver) ValidateControllerServiceRequest(c csi.ControllerServiceCapa
return nil
}
}
return status.Error(codes.InvalidArgument, fmt.Sprintf("%s", c))
return status.Error(codes.InvalidArgument, c.String())
}

func (d *CSIDriver) AddControllerServiceCapabilities(cl []csi.ControllerServiceCapability_RPC_Type) {
Expand Down
67 changes: 33 additions & 34 deletions pkg/providers/azure/provider.go
Expand Up @@ -24,7 +24,6 @@ import (
"os"
"path"
"regexp"
"strconv"
"strings"

"golang.org/x/net/context"
Expand Down Expand Up @@ -85,6 +84,14 @@ type Provider struct {
TenantID string
// POD AAD Identity flag
UsePodIdentity bool
// AAD app client secret (if not using POD AAD Identity)
AADClientSecret string
// AAD app client secret id (if not using POD AAD Identity)
AADClientID string
// the name of the pod (if using POD AAD Identity)
PodName string
// the namespace of the pod (if using POD AAD Identity)
PodNamespace string
}

type KeyVaultObject struct {
Expand Down Expand Up @@ -119,7 +126,7 @@ func ParseAzureEnvironment(cloudName string) (*azure.Environment, error) {
return &env, err
}

func (p *Provider) GetKeyvaultToken(grantType OAuthGrantType, cloudName string, aADClientSecret string, aADClientID string, podname string, podns string) (authorizer autorest.Authorizer, err error) {
func (p *Provider) GetKeyvaultToken(grantType OAuthGrantType, cloudName string) (authorizer autorest.Authorizer, err error) {
env, err := ParseAzureEnvironment(cloudName)
if err != nil {
return nil, err
Expand All @@ -129,17 +136,17 @@ func (p *Provider) GetKeyvaultToken(grantType OAuthGrantType, cloudName string,
if '/' == kvEndPoint[len(kvEndPoint)-1] {
kvEndPoint = kvEndPoint[:len(kvEndPoint)-1]
}
servicePrincipalToken, err := p.GetServicePrincipalToken(env, kvEndPoint, aADClientSecret, aADClientID, podname, podns)
servicePrincipalToken, err := p.GetServicePrincipalToken(env, kvEndPoint)
if err != nil {
return nil, err
}
authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
return authorizer, nil
}

func (p *Provider) initializeKvClient(cloudName string, aADClientSecret string, aADClientID string, podname string, podns string) (*kv.BaseClient, error) {
func (p *Provider) initializeKvClient(cloudName string) (*kv.BaseClient, error) {
kvClient := kv.New()
token, err := p.GetKeyvaultToken(AuthGrantType(), cloudName, aADClientSecret, aADClientID, podname, podns)
token, err := p.GetKeyvaultToken(AuthGrantType(), cloudName)
if err != nil {
return nil, errors.Wrapf(err, "failed to get key vault token")
}
Expand Down Expand Up @@ -173,18 +180,13 @@ func GetCredential(secrets map[string]string) (string, string, error) {
return clientID, clientSecret, nil
}

func (p *Provider) getVaultURL(ctx context.Context, cloudName string, aADClientSecret string, aADClientID string, podName string, podns string) (vaultURL *string, err error) {
func (p *Provider) getVaultURL(ctx context.Context, cloudName string) (vaultURL *string, err error) {
glog.V(5).Infof("subscriptionID: %s", p.SubscriptionID)
glog.V(5).Infof("vaultName: %s", p.KeyvaultName)
glog.V(5).Infof("resourceGroup: %s", p.ResourceGroup)

vaultsClient := kvmgmt.NewVaultsClient(p.SubscriptionID)
token, tokenErr := p.GetManagementToken(AuthGrantType(),
cloudName,
aADClientSecret,
aADClientID,
podName,
podns)
token, tokenErr := p.GetManagementToken(AuthGrantType(), cloudName)
if tokenErr != nil {
return nil, errors.Wrapf(err, "failed to get management token")
}
Expand All @@ -196,15 +198,15 @@ func (p *Provider) getVaultURL(ctx context.Context, cloudName string, aADClientS
return vault.Properties.VaultURI, nil
}

func (p *Provider) GetManagementToken(grantType OAuthGrantType, cloudName string, aADClientSecret string, aADClientID string, podname string, podns string) (authorizer autorest.Authorizer, err error) {
func (p *Provider) GetManagementToken(grantType OAuthGrantType, cloudName string) (authorizer autorest.Authorizer, err error) {

env, err := ParseAzureEnvironment(cloudName)
if err != nil {
return nil, err
}

rmEndPoint := env.ResourceManagerEndpoint
servicePrincipalToken, err := p.GetServicePrincipalToken(env, rmEndPoint, aADClientSecret, aADClientID, podname, podns)
servicePrincipalToken, err := p.GetServicePrincipalToken(env, rmEndPoint)
if err != nil {
return nil, err
}
Expand All @@ -213,7 +215,7 @@ func (p *Provider) GetManagementToken(grantType OAuthGrantType, cloudName string
}

// GetServicePrincipalToken creates a new service principal token based on the configuration
func (p *Provider) GetServicePrincipalToken(env *azure.Environment, resource string, aADClientSecret string, aADClientID string, podname string, podns string) (*adal.ServicePrincipalToken, error) {
func (p *Provider) GetServicePrincipalToken(env *azure.Environment, resource string) (*adal.ServicePrincipalToken, error) {
oauthConfig, err := adal.NewOAuthConfig(env.ActiveDirectoryEndpoint, p.TenantID)
if err != nil {
return nil, fmt.Errorf("creating the OAuth config: %v", err)
Expand All @@ -233,8 +235,8 @@ func (p *Provider) GetServicePrincipalToken(env *azure.Environment, resource str
if err != nil {
return nil, err
}
req.Header.Add(podnsheader, podns)
req.Header.Add(podnameheader, podname)
req.Header.Add(podnsheader, p.PodNamespace)
req.Header.Add(podnameheader, p.PodName)
resp, err := client.Do(req)
if err != nil {
return nil, err
Expand All @@ -259,7 +261,7 @@ func (p *Provider) GetServicePrincipalToken(env *azure.Environment, resource str
token := nmiResp.Token
clientID := nmiResp.ClientID

if &token == nil || clientID == "" {
if token.AccessToken == "" || clientID == "" {
return nil, fmt.Errorf("nmi did not return expected values in response: token and clientid")
}

Expand All @@ -274,16 +276,15 @@ func (p *Provider) GetServicePrincipalToken(env *azure.Environment, resource str
return nil, err
}
// When CSI driver is using a Service Principal clientid + client secret to retrieve token for resource
if len(aADClientSecret) > 0 {
if len(p.AADClientSecret) > 0 {
glog.V(2).Infof("azure: using client_id+client_secret to retrieve access token")
return adal.NewServicePrincipalToken(
*oauthConfig,
aADClientID,
aADClientSecret,
p.AADClientID,
p.AADClientSecret,
resource)
}

return nil, fmt.Errorf("No credentials provided for AAD application %s", aADClientID)
return nil, fmt.Errorf("No credentials provided for AAD application %s", p.AADClientID)
}

// MountSecretsStoreObjectContent mounts content of the secrets store object to target path
Expand All @@ -293,6 +294,8 @@ func (p *Provider) MountSecretsStoreObjectContent(ctx context.Context, attrib ma
resourceGroup := attrib["resourceGroup"]
subscriptionID := attrib["subscriptionId"]
tenantID := attrib["tenantId"]
p.PodName = attrib["csi.storage.k8s.io/pod.name"]
p.PodNamespace = attrib["csi.storage.k8s.io/pod.namespace"]

if keyvaultName == "" {
return fmt.Errorf("keyvaultName is not set")
Expand All @@ -308,16 +311,12 @@ func (p *Provider) MountSecretsStoreObjectContent(ctx context.Context, attrib ma
}
// defaults
usePodIdentity := false
if usePodIdentityStr != "" {
usePodIdentity, err = strconv.ParseBool(usePodIdentityStr)
if err != nil {
return fmt.Errorf("unable to parse usePodIdentity")
}
if usePodIdentityStr == "true" {
usePodIdentity = true
}
var clientID, clientSecret string
if !usePodIdentity {
glog.V(0).Infof("not using pod identity to access keyvault")
clientID, clientSecret, err = GetCredential(secrets)
p.AADClientID, p.AADClientSecret, err = GetCredential(secrets)
if err != nil {
glog.V(0).Infof("missing client credential to access keyvault")
return err
Expand Down Expand Up @@ -362,7 +361,7 @@ func (p *Provider) MountSecretsStoreObjectContent(ctx context.Context, attrib ma
p.TenantID = tenantID

for _, keyVaultObject := range keyVaultObjects {
content, err := p.GetKeyVaultObjectContent(ctx, keyVaultObject.ObjectType, keyVaultObject.ObjectName, keyVaultObject.ObjectVersion, clientID, clientSecret)
content, err := p.GetKeyVaultObjectContent(ctx, keyVaultObject.ObjectType, keyVaultObject.ObjectName, keyVaultObject.ObjectVersion)
if err != nil {
return err
}
Expand All @@ -378,15 +377,15 @@ func (p *Provider) MountSecretsStoreObjectContent(ctx context.Context, attrib ma
}

// GetKeyVaultObjectContent get content of the keyvault object
func (p *Provider) GetKeyVaultObjectContent(ctx context.Context, objectType string, objectName string, objectVersion string, clientID string, clientSecret string) (content string, err error) {
func (p *Provider) GetKeyVaultObjectContent(ctx context.Context, objectType string, objectName string, objectVersion string) (content string, err error) {
// TODO: support pod identity

vaultURL, err := p.getVaultURL(ctx, "", clientSecret, clientID, "", "")
vaultURL, err := p.getVaultURL(ctx, "")
if err != nil {
return "", errors.Wrap(err, "failed to get vault")
}

kvClient, err := p.initializeKvClient("", clientSecret, clientID, "", "")
kvClient, err := p.initializeKvClient("")
if err != nil {
return "", errors.Wrap(err, "failed to get keyvaultClient")
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/secrets-store-csi-driver/main.go
Expand Up @@ -22,7 +22,7 @@ import (

"github.com/golang/glog"

"github.com/deislabs/secrets-store-csi-driver/pkg/secrets-store"
secretsstore "github.com/deislabs/secrets-store-csi-driver/pkg/secrets-store"
)

func init() {
Expand Down
26 changes: 23 additions & 3 deletions pkg/secrets-store/nodeserver.go
Expand Up @@ -22,7 +22,8 @@ import (
"os"

"github.com/container-storage-interface/spec/lib/go/csi"
"github.com/deislabs/secrets-store-csi-driver/pkg/csi-common"

csicommon "github.com/deislabs/secrets-store-csi-driver/pkg/csi-common"
"github.com/deislabs/secrets-store-csi-driver/pkg/providers"
"github.com/deislabs/secrets-store-csi-driver/pkg/providers/register"
"github.com/golang/glog"
Expand All @@ -43,7 +44,7 @@ const (
)

func (ns *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) {
glog.V(0).Infof("NodeUnpublishVolume")
glog.V(0).Infof("NodePublishVolume")
// Check arguments
if req.GetVolumeCapability() == nil {
return nil, status.Error(codes.InvalidArgument, "Volume capability missing in request")
Expand Down Expand Up @@ -89,11 +90,29 @@ func (ns *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublis
if providerName == "" {
return nil, fmt.Errorf("providerName is not set")
}
usePodIdentity := false
usePodIdentityStr := attrib["usePodIdentity"]
if usePodIdentityStr == "true" {
usePodIdentity = true
}
if usePodIdentity {
glog.V(0).Infof("using pod identity to access keyvault")
podName := attrib["csi.storage.k8s.io/pod.name"]
podNamespace := attrib["csi.storage.k8s.io/pod.namespace"]
if podName == "" || podNamespace == "" {
return nil, fmt.Errorf("pod information is not available. deploy a CSIDriver object to set podInfoOnMount")
}
} else {
glog.V(0).Infof("not using pod identity to access keyvault")
if secrets == nil {
return nil, fmt.Errorf("unexpected: secrets is nil")
}
}
var provider providers.Provider
initConfig := register.InitConfig{}
provider, err = register.GetProvider(providerName, initConfig)
if err != nil {
glog.V(2).Infof("Error initializing provider: %s", err)
return nil, fmt.Errorf("Error initializing provider: %s", err)
}
// to ensure mount bind works, we need to mount before writing content to it
err = mounter.Mount("/tmp", targetPath, "", []string{"bind"})
Expand All @@ -103,6 +122,7 @@ func (ns *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublis
}
err = provider.MountSecretsStoreObjectContent(ctx, attrib, secrets, targetPath, permission)
if err != nil {
mounter.Unmount(targetPath)
return nil, err
}
notMnt, err = mount.New("").IsLikelyNotMountPoint(targetPath)
Expand Down
3 changes: 2 additions & 1 deletion pkg/secrets-store/secrets-store.go
Expand Up @@ -18,7 +18,8 @@ package secretsstore

import (
"github.com/container-storage-interface/spec/lib/go/csi"
"github.com/deislabs/secrets-store-csi-driver/pkg/csi-common"

csicommon "github.com/deislabs/secrets-store-csi-driver/pkg/csi-common"
"github.com/golang/glog"
)

Expand Down

0 comments on commit 5be02c5

Please sign in to comment.