Skip to content

Commit

Permalink
Add Workload Identity as an auth mechanism
Browse files Browse the repository at this point in the history
  • Loading branch information
balkrishna93 committed Apr 23, 2024
1 parent 8d6041e commit 62ddaa1
Show file tree
Hide file tree
Showing 182 changed files with 3,409 additions and 1,292 deletions.
22 changes: 20 additions & 2 deletions GettingStarted.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ The provider is a gRPC server accessible via the Unix domain socket. It's interf
* [Authentication & Authorization](#authn-authz)
* [User Principal](#auth-user-principal)
* [Instance Princiapl](#auth-instance-principal)
* [Workload Identity](#auth-workload-identity)
* [Access Policies](#access-policies)
* [Deployment](#deployment)
* [Helm](#helm-deployment)
Expand Down Expand Up @@ -49,9 +50,10 @@ This section describes steps to deploy and test solution.

<a name="authn-authz"></a>
### Authentication and Authorization
Currently, two modes of authentication is supported. Some AuthN modes are applicable only for a particular variant of cluster.
Currently, three modes of authentication is supported. Some AuthN modes are applicable only for a particular variant of cluster.
* [User Principal](#auth-user-principal)
* [Instance Principal](#auth-instance-principal)
* [Workload Identity](#auth-workload-identity)

<a name="auth-user-principal"></a>
### User Principal
Expand All @@ -73,6 +75,15 @@ kubectl create secret generic oci-config \
### Instance Principal
Instance principal would work only on OKE cluster.
Access should be granted using Access Policies(See [Access Policies](#access-polices) section).

<a name="auth-workload-identity"></a>
### Workload Identity
Workload Identity works only in OKE Enhanced clusters.

Access should be granted using Access Policies(See [Access Policies for Workloads](#access-policies-workloads) section).

Worklaod Identity uses a Resource Principal auth, which requires settings a couple ENV variables on the pod, including the region where the cluster is deployed. To achieve this, make sure to specify the `provider.oci.auth.types.workloadIdentity.enabled=true` and `provider.oci.auth.types.workloadIdentity.region=<region>` parameters in the `values.yaml` for the Helm chart deployment, or as an inline parameters.

<a name="access-policies"></a>
### Access Policies
Access to the vault and secrets should be explicity granted using Policies in case of Instance principal authencation or other users(non owner of vault) or groups of tenancy in case of user principal authentication.
Expand Down Expand Up @@ -103,6 +114,13 @@ It involves two steps

More information on [Policy](https://docs.oracle.com/en-us/iaas/Content/Identity/Concepts/policysyntax.htm)

<a name="access-policies-workload"></a>
### Access Policies for Workloads

With Workload Identity authentication, only a policy is required, which defines the kubernetes workload the policy works for:

`allow any-user to use secret-family in compartment <compartment-name> where ALL {request.principal.type='workload', request.principal.namespace ='<namespace>', request.principal.service_account = 'oci-secrets-store-csi-driver-provider-sa', request.principal.cluster_id = 'ocid1.cluster.oc1....'}`

<a name="deployment"></a>
### Deployment
Provider and Driver would be deployed as Daemonset. `kube-system` namespace is preferred, but not restricted.
Expand Down Expand Up @@ -132,7 +150,7 @@ Default values are provided in `charts/oci-secrets-store-csi-driver-provider/val
kubectl apply -f deploy/provider.daemonset.yaml
kubectl apply -f deploy/provider.serviceaccount.yaml
# if user authention principal is required
# if user authentication principal is required
kubectl apply -f deploy/provider.roles.yaml
```
<a name="provider-verification"></a>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ spec:
name: health-port
- containerPort: {{ .Values.provider.metricsPort }}
name: metrics-port
{{ if .Values.provider.oci.auth.types.workload.enabled }}
env:
- name: OCI_RESOURCE_PRINCIPAL_VERSION
value: {{ .Values.provider.oci.auth.types.workload.resourcePrincipalVersion | quote }}
- name: OCI_RESOURCE_PRINCIPAL_REGION
value: {{ .Values.provider.oci.auth.types.workload.resourcePrincipalRegion }}
{{ end }}
resources:
{{- toYaml .Values.provider.resources | nindent 12 }}
# Container should run as root to mount the hostPath volume and create Unix Domain Socket in that volume.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,29 @@ subjects:
- kind: ServiceAccount
name: {{ .Chart.Name }}-sa
namespace: {{ .Release.Namespace }}
{{ end }}
{{ end }}

{{ if .Values.provider.oci.auth.types.workload.enabled }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: {{ .Chart.Name }}-workload-identity-cluster-role
rules:
- apiGroups: [""]
resources: ["serviceaccounts/token"]
verbs: ["create"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: {{ .Chart.Name }}-workload-identity-cluster-rolebinding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ .Chart.Name }}-workload-identity-cluster-role
subjects:
- kind: ServiceAccount
name: {{ .Chart.Name }}-sa
namespace: {{ .Release.Namespace }}
{{ end }}
18 changes: 18 additions & 0 deletions charts/oci-secrets-store-csi-driver-provider/values.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,24 @@
}
}
},
"workload": {
"description": "Settings for OCI Workload authentication",
"type": "object",
"properties": {
"enabled": {
"description": "Settings for OCI Workload authentication",
"type": "boolean"
},
"resourcePrincipalVersion": {
"description": "Settings for OCI Workload authentication",
"type": "string"
},
"resourcePrincipalRegion": {
"description": "Settings for OCI Workload authentication",
"type": "string"
}
}
},
"additionalProperties": false
}
},
Expand Down
5 changes: 5 additions & 0 deletions charts/oci-secrets-store-csi-driver-provider/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ provider:
enabled: true
user:
enabled: true
workload:
enabled: true
resourcePrincipalVersion: "2.2"
resourcePrincipalRegion: "sa-bogota-1"


# socket endpoint for connections
endpoint: "unix:///opt/provider/sockets/oci.sock"
Expand Down
2 changes: 2 additions & 0 deletions deploy/example/app.deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ spec:
labels:
app: nginx
spec:
# serviceAccountName: workload-serviceaccount
# automountServiceAccountToken: true
containers:
- name: nginx
image: nginx:1.21.4-alpine
Expand Down
2 changes: 1 addition & 1 deletion deploy/example/secret-provider-class.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,5 @@ spec:
versionNumber: 1
fileName: src-db-password
vaultId: ocid1.vault.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
authType: instance # possible values are: user, instance
authType: instance # possible values are: user, instance, workload
authSecretName: oci-config # required if authType is user and this value refers secret name contains user credentials for auth against vault
5 changes: 5 additions & 0 deletions deploy/provider.daemonset.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ spec:
- --metrics-port=8198
- --enable-pprof=true
- --pprof-port=6060
env:
- name: OCI_RESOURCE_PRINCIPAL_VERSION
value: "2.2"
- name: OCI_RESOURCE_PRINCIPAL_REGION
value: "us-ashburn-1"
resources:
requests:
cpu: 50m
Expand Down
3 changes: 3 additions & 0 deletions deploy/provider.roles.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get"]
- apiGroups: [""]
resources: ["serviceaccounts/token"]
verbs: ["create"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/oracle-samples/oci-secrets-store-csi-driver-provider
go 1.19

require (
github.com/oracle/oci-go-sdk/v65 v65.3.0
github.com/oracle/oci-go-sdk/v65 v65.61.1
github.com/pkg/errors v0.9.1
github.com/rs/zerolog v1.26.1
go.opentelemetry.io/otel v0.20.0
Expand Down Expand Up @@ -51,7 +51,7 @@ require (
go.opentelemetry.io/otel/trace v0.20.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/oauth2 v0.4.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/term v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect
Expand Down
18 changes: 11 additions & 7 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -321,8 +321,8 @@ github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxS
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/oracle/oci-go-sdk/v65 v65.3.0 h1:O8QvjQHyKeIxxyLBkv0w7NAmwPSDeIg/eZCevKMNq3s=
github.com/oracle/oci-go-sdk/v65 v65.3.0/go.mod h1:oyMrMa1vOzzKTmPN+kqrTR9y9kPA2tU1igN3NUSNTIE=
github.com/oracle/oci-go-sdk/v65 v65.61.1 h1:5N65lmT+NAeoS69Se0TLbAcylLyZ8jR/iuo1j+exXMk=
github.com/oracle/oci-go-sdk/v65 v65.61.1/go.mod h1:IBEV9l1qBzUpo7zgGaRUhbB05BVfcDGYRFBCPlTcPp0=
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
Expand Down Expand Up @@ -397,15 +397,20 @@ github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
Expand Down Expand Up @@ -593,9 +598,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/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/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
Expand Down
76 changes: 76 additions & 0 deletions internal/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"encoding/json"
"fmt"
"strconv"
"time"

"os"

Expand All @@ -21,8 +22,10 @@ import (
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"gopkg.in/yaml.v3"
authenticationv1 "k8s.io/api/authentication/v1"
core "k8s.io/api/core/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
apiMachineryTypes "k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
provider "sigs.k8s.io/secrets-store-csi-driver/provider/v1alpha1"
Expand Down Expand Up @@ -52,6 +55,8 @@ const vaultIDField = "vaultId"
const secretProviderClassField = "secretProviderClass"
const podNameField = "csi.storage.k8s.io/pod.name"
const podNamespaceField = "csi.storage.k8s.io/pod.namespace"
const podUIDField = "csi.storage.k8s.io/pod.uid"
const podServiceAccountField = "csi.storage.k8s.io/serviceAccount.name"

// BuildVersion set during the build with ldflags
var BuildVersion string
Expand Down Expand Up @@ -166,6 +171,32 @@ func (server *ProviderServer) retrieveAuthConfig(ctx context.Context,
return nil, fmt.Errorf("missing auth config data: %v", err)
}
auth.Config = *authCfg
} else if principalType == types.Workload {

podInfo := &types.PodInfo{
Name: requestAttributes[podNameField],
UID: apiMachineryTypes.UID(requestAttributes[podUIDField]),
ServiceAccountName: requestAttributes[podServiceAccountField],
Namespace: requestAttributes[podNamespaceField],
}
saTokenStr, err := server.getSAToken(podInfo)
if err != nil {
err := fmt.Errorf("can not generate token for service account: %s, namespace: %s, Error: %v",
podInfo.ServiceAccountName, podInfo.Namespace, err)
return nil, err
}

//TO-DO validate region
//region := requestAttributes[regionField]
//if err != nil {
//err := fmt.Errorf("can not create resource principal, region is missing")
//return nil, resourcePrincipalError{err: err}
//}

auth.WorkloadIdentityCfg = types.WorkloadIdentityConfig{
SaToken: []byte(saTokenStr),
// Region: region,
}
}
return auth, nil
}
Expand Down Expand Up @@ -195,6 +226,51 @@ func parseAuthConfig(secret *core.Secret, authConfigSecretName string) (*types.A
return authCfg, nil
}

func (server *ProviderServer) getK8sClientSet() (*kubernetes.Clientset, error) {
clusterCfg, err := rest.InClusterConfig()
if err != nil {
return nil, fmt.Errorf("can not get cluster config. error: %v", err)
}

clientset, err := kubernetes.NewForConfig(clusterCfg)
if err != nil {
return nil, fmt.Errorf("can not initialize kubernetes client. error: %v", err)
}

return clientset, nil
}

func (server *ProviderServer) getSAToken(podInfo *types.PodInfo) (string, error) {
clientSet, err := server.getK8sClientSet()
if err != nil {
return "", fmt.Errorf("unable to get k8s client: %v", err)
}
ttl := int64((15 * time.Minute).Seconds())
resp, err := clientSet.CoreV1().
ServiceAccounts(podInfo.Namespace).
CreateToken(context.Background(), podInfo.ServiceAccountName,
&authenticationv1.TokenRequest{
Spec: authenticationv1.TokenRequestSpec{
ExpirationSeconds: &ttl,
Audiences: []string{"oci", "api"},
BoundObjectRef: &authenticationv1.BoundObjectReference{
Kind: "Pod",
APIVersion: "v1",
Name: podInfo.Name,
UID: podInfo.UID,
},
},
},
meta.CreateOptions{},
)
if err != nil {
return "", fmt.Errorf("unable to fetch token from token api: %v", err)
}
// fmt.Printf("\nToken Response: %v", resp)
// fmt.Printf("\nToken: %v", resp.Status.Token)
return resp.Status.Token, nil
}

func (server *ProviderServer) readK8sSecret(ctx context.Context, namespace string,
secretName string) (*core.Secret, error) {
clusterCfg, err := rest.InClusterConfig()
Expand Down
4 changes: 4 additions & 0 deletions internal/service/secret_client_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ func (factory *OCISecretClientFactory) createConfigProvider( //nolint:ireturn //
return common.NewRawConfigurationProvider(cfg.TenancyID, cfg.UserID,
cfg.Region, cfg.Fingerprint, cfg.PrivateKey, &cfg.Passphrase), nil

case types.Workload:
return auth.OkeWorkloadIdentityConfigurationProviderWithServiceAccountTokenProvider(
auth.NewSuppliedServiceAccountTokenProvider(string(authCfg.WorkloadIdentityCfg.SaToken)))

default:
return nil, fmt.Errorf("unable to determine OCI principal type for configuration provider")
}
Expand Down
4 changes: 4 additions & 0 deletions internal/service/secret_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/oracle-samples/oci-secrets-store-csi-driver-provider/internal/testutils"
"github.com/oracle-samples/oci-secrets-store-csi-driver-provider/internal/types"
"github.com/oracle/oci-go-sdk/v65/common"
"github.com/oracle/oci-go-sdk/v65/common/auth"
"github.com/oracle/oci-go-sdk/v65/secrets"
)

Expand All @@ -40,6 +41,9 @@ func (factory *MockOCISecretClientFactory) createConfigProvider( //nolint:iretur
return common.NewRawConfigurationProvider("tenancy", "user", "region", "fingerprint", "privatekey", nil), nil
case types.Instance:
return common.NewRawConfigurationProvider("tenancy", "user", "region", "fingerprint", "privatekey", nil), nil
case types.Workload:
return auth.OkeWorkloadIdentityConfigurationProviderWithServiceAccountTokenProvider(
auth.NewSuppliedServiceAccountTokenProvider(string(authCfg.WorkloadIdentityCfg.SaToken)))
default:
return nil, fmt.Errorf("unable to determine OCI principal type for configuration provider")
}
Expand Down
Loading

0 comments on commit 62ddaa1

Please sign in to comment.