Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adjust the provider to use the available service Account annotations instead of requiring it again in the SPC parameters #1443

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ require (
golang.org/x/net v0.17.0
google.golang.org/grpc v1.59.0
gopkg.in/yaml.v3 v3.0.1
k8s.io/client-go v0.25.3
k8s.io/component-base v0.25.3
k8s.io/klog/v2 v2.80.1
sigs.k8s.io/secrets-store-csi-driver v1.3.4
Expand All @@ -35,18 +36,32 @@ require (
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/emicklei/go-restful/v3 v3.8.0 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-logr/zapr v1.2.3 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.5 // indirect
github.com/go-openapi/swag v0.19.14 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/gnostic v0.5.7-v3refs // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/uuid v1.3.1 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.12.2 // indirect
Expand All @@ -62,10 +77,21 @@ require (
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/oauth2 v0.11.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/term v0.16.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/api v0.25.3 // indirect
k8s.io/apimachinery v0.25.3 // indirect
k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect
k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 // indirect
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)
64 changes: 64 additions & 0 deletions go.sum

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,13 @@ rules:
verbs: ["get", "watch", "list"]
{{- end }}
{{- end }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: {{ template "sscdpa.fullname" . }}-cluster-role
{{ include "sscdpa.labels" . | indent 2 }}
rules:
- apiGroups: [""]
resources: ["serviceaccounts"]
verbs: ["get"]
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,17 @@ subjects:
namespace: {{ .Release.Namespace }}
{{- end }}
{{- end }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: {{ template "sscdpa.fullname" . }}-cluster-role-binding
{{ include "sscdpa.labels" . | indent 2 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ template "sscdpa.fullname" . }}-cluster-role
subjects:
- kind: ServiceAccount
name: csi-secrets-store-provider-azure
namespace: {{ .Release.Namespace }}
57 changes: 57 additions & 0 deletions pkg/provider/kuberneteshelper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package provider

import (
"context"
"fmt"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8sv1 "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/rest"
)

const (
// ServiceAccountClientID annotation
ServiceAccountClientID = "azure.workload.identity/client-id"
)

type KubernetesHelper struct {
nameSpace, svcAcc string
k8sClient k8sv1.CoreV1Interface
}

func NewKubernetesHelper(nameSpace, svcAcc string) KubernetesHelper {
k8sClient, err := getK8sClient()
if err != nil {
panic(err)
}

return KubernetesHelper{
nameSpace: nameSpace,
svcAcc: svcAcc,
k8sClient: k8sClient,
}
}

func (k KubernetesHelper) GetServiceAccountClientID() (string, error) {
serviceAccount, err := k.k8sClient.ServiceAccounts(k.nameSpace).Get(context.Background(), k.svcAcc, metav1.GetOptions{})
if err != nil {
return "", fmt.Errorf("failed to get service account %s in namespace %s, error: %w", k.svcAcc, k.nameSpace, err)
}
clientID, ok := serviceAccount.Annotations[ServiceAccountClientID]
if !ok {
return "", fmt.Errorf("clientID not found in service account %s in namespace %s", k.svcAcc, k.nameSpace)
}
return clientID, nil
}

func getK8sClient() (k8sv1.CoreV1Interface, error) {
config, err := rest.InClusterConfig()
if err != nil {
return nil, fmt.Errorf("failed to get in cluster config, error: %w", err)
}
k8sClient, err := k8sv1.NewForConfig(config)
if err != nil {
panic(err)
}
return k8sClient, nil
}
7 changes: 6 additions & 1 deletion pkg/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ func (p *provider) GetSecretsStoreObjectContent(ctx context.Context, attrib, sec
cloudEnvFileName := types.GetCloudEnvFileName(attrib)
podName := types.GetPodName(attrib)
podNamespace := types.GetPodNamespace(attrib)
saName := types.GetServiceAccountName(attrib)

usePodIdentity, err := types.GetUsePodIdentity(attrib)
if err != nil {
Expand All @@ -134,7 +135,11 @@ func (p *provider) GetSecretsStoreObjectContent(ctx context.Context, attrib, sec
}

// attributes for workload identity
workloadIdentityClientID := types.GetClientID(attrib)
kubernetesHelper := NewKubernetesHelper(podNamespace, saName)
workloadIdentityClientID, err := kubernetesHelper.GetServiceAccountClientID()
if err != nil {
return nil, fmt.Errorf("failed to get service account client id, error: %w", err)
}
saTokens := types.GetServiceAccountTokens(attrib)

if keyvaultName == "" {
Expand Down
10 changes: 5 additions & 5 deletions pkg/provider/types/parameters.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,16 +70,16 @@ func GetPodNamespace(parameters map[string]string) string {
return strings.TrimSpace(parameters[CSIAttributePodNamespace])
}

// GetClientID returns the client ID
func GetClientID(parameters map[string]string) string {
return strings.TrimSpace(parameters[ClientIDParameter])
}

// GetServiceAccountTokens returns the service account tokens
func GetServiceAccountTokens(parameters map[string]string) string {
return strings.TrimSpace(parameters[CSIAttributeServiceAccountTokens])
}

// GetServiceAccountName returns the service account name
func GetServiceAccountName(parameters map[string]string) string {
return strings.TrimSpace(parameters[CSIAttributeServiceAccountName])
}

// GetObjects returns the key vault objects
func GetObjects(parameters map[string]string) string {
return strings.TrimSpace(parameters[ObjectsParameter])
Expand Down
24 changes: 12 additions & 12 deletions pkg/provider/types/parameters_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ func TestGetPodNamespace(t *testing.T) {
}
}

func TestGetClientID(t *testing.T) {
func TestGetServiceAccountTokens(t *testing.T) {
tests := []struct {
name string
parameters map[string]string
Expand All @@ -447,37 +447,37 @@ func TestGetClientID(t *testing.T) {
{
name: "empty",
parameters: map[string]string{
"clientID": "",
CSIAttributeServiceAccountTokens: "",
},
expected: "",
},
{
name: "not empty",
parameters: map[string]string{
"clientID": "test",
CSIAttributeServiceAccountTokens: "test",
},
expected: "test",
},
{
name: "trim spaces",
parameters: map[string]string{
"clientID": " test ",
CSIAttributeServiceAccountTokens: " test ",
},
expected: "test",
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
actual := GetClientID(test.parameters)
actual := GetServiceAccountTokens(test.parameters)
if actual != test.expected {
t.Errorf("GetClientID() = %v, expected %v", actual, test.expected)
t.Errorf("GetServiceAccountTokens() = %v, expected %v", actual, test.expected)
}
})
}
}

func TestGetServiceAccountTokens(t *testing.T) {
func TestGetServiceAccountName(t *testing.T) {
tests := []struct {
name string
parameters map[string]string
Expand All @@ -486,31 +486,31 @@ func TestGetServiceAccountTokens(t *testing.T) {
{
name: "empty",
parameters: map[string]string{
CSIAttributeServiceAccountTokens: "",
CSIAttributeServiceAccountName: "",
},
expected: "",
},
{
name: "not empty",
parameters: map[string]string{
CSIAttributeServiceAccountTokens: "test",
CSIAttributeServiceAccountName: "test",
},
expected: "test",
},
{
name: "trim spaces",
parameters: map[string]string{
CSIAttributeServiceAccountTokens: " test ",
CSIAttributeServiceAccountName: " test ",
},
expected: "test",
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
actual := GetServiceAccountTokens(test.parameters)
actual := GetServiceAccountName(test.parameters)
if actual != test.expected {
t.Errorf("GetServiceAccountTokens() = %v, expected %v", actual, test.expected)
t.Errorf("GetServiceAccountName() = %v, expected %v", actual, test.expected)
}
})
}
Expand Down
1 change: 1 addition & 0 deletions pkg/provider/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const (
CSIAttributePodName = "csi.storage.k8s.io/pod.name"
CSIAttributePodNamespace = "csi.storage.k8s.io/pod.namespace"
CSIAttributeServiceAccountTokens = "csi.storage.k8s.io/serviceAccount.tokens" // nolint
CSIAttributeServiceAccountName = "csi.storage.k8s.io/serviceAccount.name"

// KeyVaultNameParameter is the name of the key vault name parameter
KeyVaultNameParameter = "keyvaultName"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ spec:
provider: azure
parameters:
usePodIdentity: "false" # set to true for pod identity access mode
clientID: "<client id of the Azure AD Application or user-assigned managed identity to use for workload identity>"
wenzel-felix marked this conversation as resolved.
Show resolved Hide resolved
keyvaultName: "kvname"
cloudName: "" # [OPTIONAL for Azure] if not provided, azure environment will default to AzurePublicCloud
objects: |
Expand Down Expand Up @@ -172,10 +171,12 @@ az identity federated-credential create \

### 4. Deploy your secretproviderclass and application

Set the `clientID` in the `SecretProviderClass` to the client ID of the AAD application or user-assigned managed identity.
Set the `azure.workload.identity/client-id` annotation in the `ServiceAccount` of your pod to the client ID of the AAD application or user-assigned managed identity.

```yaml
clientID: "${APPLICATION_OR_MANAGED_IDENTITY_CLIENT_ID}"
metadata:
annoations:
azure.workload.identity/client-id: "${APPLICATION_OR_MANAGED_IDENTITY_CLIENT_ID}"
```

## Pros
Expand Down
2 changes: 0 additions & 2 deletions website/content/en/getting-started/usage/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ To provide identity to access Key Vault, refer to the following [section](#provi
usePodIdentity: "false" # [OPTIONAL] if not provided, will default to "false"
useVMManagedIdentity: "false" # [OPTIONAL available for version > 0.0.4] if not provided, will default to "false"
userAssignedIdentityID: "client_id" # [OPTIONAL available for version > 0.0.4] use the client id to specify which user assigned managed identity to use. If using a user assigned identity as the VM's managed identity, then specify the identity's client id. If empty, then defaults to use the system assigned identity on the VM
clientID: "client_id" # [OPTIONAL available for version > 1.1.0] client id of the Azure AD Application or managed identity to use for workload identity
keyvaultName: "kvname" # the name of the KeyVault
cloudName: "" # [OPTIONAL available for version > 0.0.4] if not provided, azure environment will default to AzurePublicCloud
cloudEnvFileName: "" # [OPTIONAL available for version > 0.0.7] use to define path to file for populating azure environment
Expand Down Expand Up @@ -69,7 +68,6 @@ To provide identity to access Key Vault, refer to the following [section](#provi
| usePodIdentity | no | set to true for using aad-pod-identity to access keyvault | "false" |
| useVMManagedIdentity | no | [__*available for version > 0.0.4*__] specify access mode to enable use of User-assigned managed identity | "false" |
| userAssignedIdentityID | no | [__*available for version > 0.0.4*__] the user assigned identity ID is required for User-assigned Managed Identity mode | "" |
| clientID | no | [__*available for version > 1.1.0*__] client id of the Azure AD Application or managed identity to use for workload identity | "" |
wenzel-felix marked this conversation as resolved.
Show resolved Hide resolved
| keyvaultName | yes | name of a Key Vault instance | "" |
| cloudName | no | [__*available for version > 0.0.4*__] name of the azure cloud based on azure go sdk (AzurePublicCloud, AzureUSGovernmentCloud, AzureChinaCloud, AzureGermanCloud, AzureStackCloud) | "" |
| cloudEnvFileName | no | [__*available for version > 0.0.7*__] path to the file to be used while populating the Azure Environment (required if target cloud is AzureStackCloud). More details [here](../../configurations/custom-environments). | "" |
Expand Down