Skip to content

Commit

Permalink
Added support for Cloud Identity
Browse files Browse the repository at this point in the history
  • Loading branch information
prajwalv-netapp authored Jan 12, 2024
1 parent 5e3b960 commit e8a7ca1
Show file tree
Hide file tree
Showing 16 changed files with 907 additions and 94 deletions.
52 changes: 42 additions & 10 deletions cli/cmd/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ var (
acpImage string
enableACP bool
cloudProvider string
cloudIdentity string

// CLI-based K8S client
client k8sclient.KubernetesClient
Expand Down Expand Up @@ -152,6 +153,7 @@ var (
appLabel string
appLabelKey string
appLabelValue string
identityLabel bool

persistentObjectLabelKey string
persistentObjectLabelValue string
Expand Down Expand Up @@ -231,6 +233,7 @@ func init() {
"Override the default trident-acp container image.")

installCmd.Flags().StringVar(&cloudProvider, "cloud-provider", "", "Name of the cloud provider")
installCmd.Flags().StringVar(&cloudIdentity, "cloud-identity", "", "Cloud identity to be set on service account")

if err := installCmd.Flags().MarkHidden("skip-k8s-version-check"); err != nil {
_, _ = fmt.Fprintln(os.Stderr, err)
Expand Down Expand Up @@ -433,6 +436,33 @@ func validateInstallationArguments() error {
return fmt.Errorf("'%s' is not a valid trident image pull policy", imagePullPolicy)
}

// Validate the cloud provider
if !(cloudProvider == "" || strings.EqualFold(cloudProvider, k8sclient.CloudProviderAzure) || strings.EqualFold(cloudProvider, k8sclient.CloudProviderAWS)) {
return fmt.Errorf("'%s' is not a valid cloud provider ", cloudProvider)
}

// Validate the flags to set the Workload Identity/IAM Role as annotation.
// cloud provider cannot be empty.
if cloudProvider == "" && cloudIdentity != "" {
return fmt.Errorf("cloud provider must be specified for the cloud identity '%s'", cloudIdentity)
}

// validate the cloud provider and cloud identity for Azure.
if strings.EqualFold(cloudProvider, k8sclient.CloudProviderAzure) && strings.Contains(cloudIdentity, k8sclient.AzureCloudIdentityKey) {
// Add identity label.
identityLabel = true

// Set the value of cloud provider as empty to avoid updating deployment yaml.
cloudProvider = ""
} else if strings.EqualFold(cloudProvider, k8sclient.CloudProviderAzure) && cloudIdentity != "" && !strings.Contains(cloudIdentity, k8sclient.AzureCloudIdentityKey) {
return fmt.Errorf("'%s' is not a valid cloud identity for the cloud provider '%s'", cloudIdentity, k8sclient.CloudProviderAzure)
}

// validate the cloud provider and cloud identity for AWS.
if strings.EqualFold(cloudProvider, k8sclient.CloudProviderAWS) && !strings.Contains(cloudIdentity, k8sclient.AWSCloudIdentityKey) {
return fmt.Errorf("'%s' is not a valid cloud identity for the cloud provider '%s'", cloudIdentity, k8sclient.CloudProviderAWS)
}

return nil
}

Expand Down Expand Up @@ -533,7 +563,7 @@ func prepareYAMLFiles() error {
// Creating Controller RBAC objects
// Creating service account for controller
controllerServiceAccountYAML := k8sclient.GetServiceAccountYAML(getControllerRBACResourceName(), nil, labels,
nil)
nil, cloudIdentity)
if err = writeFile(controllerServiceAccountPath, controllerServiceAccountYAML); err != nil {
return fmt.Errorf("could not write controller service account YAML file; %v", err)
}
Expand Down Expand Up @@ -566,7 +596,7 @@ func prepareYAMLFiles() error {
// Creating Linux Node RBAC objects
// Creating service account for node linux
nodeServiceAccountYAML := k8sclient.GetServiceAccountYAML(getNodeRBACResourceName(false),
nil, daemonSetlabels, nil)
nil, daemonSetlabels, nil, "")
if err = writeFile(nodeLinuxServiceAccountPath, nodeServiceAccountYAML); err != nil {
return fmt.Errorf("could not write node linux service account YAML file; %v", err)
}
Expand Down Expand Up @@ -618,6 +648,7 @@ func prepareYAMLFiles() error {
ACPImage: acpImage,
EnableACP: enableACP,
CloudProvider: cloudProvider,
IdentityLabel: identityLabel,
}
deploymentYAML := k8sclient.GetCSIDeploymentYAML(deploymentArgs)
if err = writeFile(deploymentPath, deploymentYAML); err != nil {
Expand Down Expand Up @@ -710,7 +741,7 @@ func prepareYAMLFiles() error {
// Creating node windows RBAC objects
// Creating service account for node windows
nodeWindowsServiceAccountYAML := k8sclient.GetServiceAccountYAML(getNodeRBACResourceName(true), nil,
daemonSetlabels, nil)
daemonSetlabels, nil, "")
if err = writeFile(nodeWindowsServiceAccountPath, nodeWindowsServiceAccountYAML); err != nil {
return fmt.Errorf("could not write node windows service account YAML file; %v", err)
}
Expand Down Expand Up @@ -1042,6 +1073,7 @@ func installTrident() (returnError error) {
ACPImage: acpImage,
EnableACP: enableACP,
CloudProvider: cloudProvider,
IdentityLabel: identityLabel,
}
returnError = client.CreateObjectByYAML(
k8sclient.GetCSIDeploymentYAML(deploymentArgs))
Expand Down Expand Up @@ -1474,7 +1506,7 @@ func createRBACObjects() (returnError error) {
// Creating controller RBAC Objects
// Create service account for controller
if createObjectFunc(controllerServiceAccountPath,
k8sclient.GetServiceAccountYAML(getControllerRBACResourceName(), nil, labels, nil)) != nil {
k8sclient.GetServiceAccountYAML(getControllerRBACResourceName(), nil, labels, nil, cloudIdentity)) != nil {
returnError = fmt.Errorf("could not create controller service account; %v", returnError)
return
}
Expand Down Expand Up @@ -1517,7 +1549,7 @@ func createRBACObjects() (returnError error) {
// Creating node linux RBAC Objects
// Create service account for node linux
if createObjectFunc(nodeLinuxServiceAccountPath,
k8sclient.GetServiceAccountYAML(getNodeRBACResourceName(false), nil, daemonSetlabels, nil)) != nil {
k8sclient.GetServiceAccountYAML(getNodeRBACResourceName(false), nil, daemonSetlabels, nil, "")) != nil {
returnError = fmt.Errorf("could not create node linux service account; %v", returnError)
return
}
Expand Down Expand Up @@ -1548,7 +1580,7 @@ func createRBACObjects() (returnError error) {
// Creating node windows RBAC Objects
if windows {
if createObjectFunc(nodeWindowsServiceAccountPath,
k8sclient.GetServiceAccountYAML(getNodeRBACResourceName(true), nil, daemonSetlabels, nil)) != nil {
k8sclient.GetServiceAccountYAML(getNodeRBACResourceName(true), nil, daemonSetlabels, nil, "")) != nil {
returnError = fmt.Errorf("could not create node windows service account; %v", returnError)
return
}
Expand Down Expand Up @@ -1629,7 +1661,7 @@ func removeRBACObjects(logLevel log.Level) (anyErrors bool) {
// Remove RBAC objects of name 'trident-csi' from previous installations if found
// Delete service account
deleteObjectFunc(
k8sclient.GetServiceAccountYAML(getServiceAccountName(), nil, nil, nil),
k8sclient.GetServiceAccountYAML(getServiceAccountName(), nil, nil, nil, cloudIdentity),
"Could not delete trident-csi service account.",
"Deleted trident-csi service account.",
)
Expand All @@ -1655,7 +1687,7 @@ func removeRBACObjects(logLevel log.Level) (anyErrors bool) {
// DELETING Controller RBAC objects
// Delete controller service account
deleteObjectFunc(
k8sclient.GetServiceAccountYAML(getControllerRBACResourceName(), nil, labels, nil),
k8sclient.GetServiceAccountYAML(getControllerRBACResourceName(), nil, labels, nil, cloudIdentity),
"Could not delete controller service account.",
"Deleted controller service account.",
)
Expand Down Expand Up @@ -1692,7 +1724,7 @@ func removeRBACObjects(logLevel log.Level) (anyErrors bool) {
// DELETING node linux RBAC objects
// Delete node linux service account
deleteObjectFunc(
k8sclient.GetServiceAccountYAML(getNodeRBACResourceName(false), nil, daemonSetlabels, nil),
k8sclient.GetServiceAccountYAML(getNodeRBACResourceName(false), nil, daemonSetlabels, nil, ""),
"Could not delete node linux service account.",
"Deleted node linux service account.",
)
Expand Down Expand Up @@ -1728,7 +1760,7 @@ func removeRBACObjects(logLevel log.Level) (anyErrors bool) {
// DELETING node windows RBAC objects
// Delete node windows service account
deleteObjectFunc(
k8sclient.GetServiceAccountYAML(getNodeRBACResourceName(true), nil, daemonSetlabels, nil),
k8sclient.GetServiceAccountYAML(getNodeRBACResourceName(true), nil, daemonSetlabels, nil, ""),
"Could not delete node windows service account.",
"Deleted node windows service account.",
)
Expand Down
43 changes: 43 additions & 0 deletions cli/cmd/install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,3 +227,46 @@ metadata:
name: %v
spec:
group: trident.netapp.io`

func TestValidateInstallationArguments(t *testing.T) {
tests := []struct {
TridentPodNamespace string
logFormat string
imagePullPolicy string
cloudProvider string
cloudIdentity string
Valid bool
}{
// Valid arguments
{"default", "text", "IfNotPresent", "", "", true},
{"test-namespace", "text", "IfNotPresent", "", "", true},
{"test-namespace", "json", "Never", "", "", true},
{"test-namespace", "json", "Never", k8sclient.CloudProviderAzure, "", true},
{"test", "text", "Always", k8sclient.CloudProviderAzure, k8sclient.AzureCloudIdentityKey + " a8rry78r8-7733-49bd-6656582", true},
{"test", "text", "Always", k8sclient.CloudProviderAzure, "", true},

// Invalid arguments
{"", "", "", "", "", false},
{"test", "html", "", "", "", false},
{"test", "text", "Anyways", "", "", false},
{"test", "json", "Never", "Docker", "", false},
{"test", "json", "Never", "Docker", k8sclient.AzureCloudIdentityKey + " a8rry78r8-7733-49bd-6656582", false},
{"test", "text", "IfNotPresent", k8sclient.CloudProviderAWS, "", false},
{"test", "text", "IfNotPresent", "", k8sclient.AzureCloudIdentityKey + " a8rry78r8-7733-49bd-6656582", false},
{"test", "text", "Always", k8sclient.CloudProviderAzure, "a8rry78r8-7733-49bd-6656582", false},
}

for _, test := range tests {
TridentPodNamespace = test.TridentPodNamespace
logFormat = test.logFormat
imagePullPolicy = test.imagePullPolicy
cloudProvider = test.cloudProvider
cloudIdentity = test.cloudIdentity
err := validateInstallationArguments()
if test.Valid {
assert.NoError(t, err, "should be valid")
} else {
assert.Error(t, err, "should be invalid")
}
}
}
6 changes: 5 additions & 1 deletion cli/k8s_client/k8s_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,11 @@ const (
// CRD Finalizer name
TridentFinalizer = "trident.netapp.io"

CloudProviderAzure = "Azure"
CloudProviderAzure = "Azure"
CloudProviderAWS = "AWS"
AzureCloudIdentityKey = "azure.workload.identity/client-id:"
AzureWorkloadIdentityLabel = "azure.workload.identity/use: 'true'"
AWSCloudIdentityKey = "eks.amazonaws.com/role-arn:"
)

type LogLineCallback func(string)
Expand Down
1 change: 1 addition & 0 deletions cli/k8s_client/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ type DeploymentYAMLArguments struct {
ACPImage string `json:"acpImage"`
EnableACP bool `json:"enableACP"`
CloudProvider string `json:"cloudProvider"`
IdentityLabel bool `json:"identityLabel"`
}

type DaemonsetYAMLArguments struct {
Expand Down
29 changes: 26 additions & 3 deletions cli/k8s_client/yaml_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ metadata:
`

func GetServiceAccountYAML(
serviceAccountName string, secrets []string, labels, controllingCRDetails map[string]string,
serviceAccountName string, secrets []string, labels, controllingCRDetails map[string]string, cloudIdentity string,
) string {
var saYAML string
Log().WithFields(LogFields{
Expand All @@ -51,11 +51,17 @@ func GetServiceAccountYAML(
saYAML = serviceAccountYAML
}

if cloudIdentity != "" {
saYAML = strings.ReplaceAll(saYAML, "{CLOUD_IDENTITY}", constructServiceAccountAnnotation(cloudIdentity))
} else {
saYAML = strings.ReplaceAll(saYAML, "{CLOUD_IDENTITY}", "")
}

saYAML = strings.ReplaceAll(saYAML, "{NAME}", serviceAccountName)
saYAML = replaceMultilineYAMLTag(saYAML, "LABELS", constructLabels(labels))
saYAML = replaceMultilineYAMLTag(saYAML, "OWNER_REF", constructOwnerRef(controllingCRDetails))

// Log().ervice account YAML before the secrets are added.
// Log().service account YAML before the secrets are added.
Log().WithField("yaml", saYAML).Trace("Service account YAML.")
saYAML = strings.Replace(saYAML, "{SECRETS}", constructServiceAccountSecrets(secrets), 1)
return saYAML
Expand All @@ -66,6 +72,7 @@ apiVersion: v1
kind: ServiceAccount
metadata:
name: {NAME}
{CLOUD_IDENTITY}
{LABELS}
{OWNER_REF}
`
Expand All @@ -75,6 +82,7 @@ apiVersion: v1
kind: ServiceAccount
metadata:
name: {NAME}
{CLOUD_IDENTITY}
{LABELS}
{OWNER_REF}
{SECRETS}
Expand Down Expand Up @@ -456,6 +464,12 @@ func GetCSIDeploymentYAML(args *DeploymentYAMLArguments) string {
autosupportInsecureLine = "- -insecure"
}

if args.IdentityLabel {
deploymentYAML = strings.ReplaceAll(deploymentYAML, "{LABEL_IDENTITY}", AzureWorkloadIdentityLabel)
} else {
deploymentYAML = strings.ReplaceAll(deploymentYAML, "{LABEL_IDENTITY}", "")
}

if args.EnableACP {
enableACP = "- \"-enable_acp\""
deploymentYAML = strings.ReplaceAll(deploymentYAML, "{ACP_YAML}", acpContainerYAMLTemplate)
Expand All @@ -466,7 +480,8 @@ func GetCSIDeploymentYAML(args *DeploymentYAMLArguments) string {

if strings.EqualFold(args.CloudProvider, CloudProviderAzure) {
deploymentYAML = strings.ReplaceAll(deploymentYAML, "{AZURE_CREDENTIAL_FILE_ENV}", "- name: AZURE_CREDENTIAL_FILE\n value: /etc/kubernetes/azure.json")
deploymentYAML = strings.ReplaceAll(deploymentYAML, "{AZURE_CREDENTIAL_FILE_VOLUME}", "- name: azure-cred\n hostPath:\n path: /etc/kubernetes\n type: DirectoryOrCreate")
deploymentYAML = strings.ReplaceAll(deploymentYAML, "{AZURE_CREDENTIAL_FILE_VOLUME}",
"- name: azure-cred\n hostPath:\n path: /etc/kubernetes\n type: DirectoryOrCreate")
deploymentYAML = strings.ReplaceAll(deploymentYAML, "{AZURE_CREDENTIAL_FILE_VOLUME_MOUNT}", "- name: azure-cred\n mountPath: /etc/kubernetes")
} else {
deploymentYAML = strings.ReplaceAll(deploymentYAML, "{AZURE_CREDENTIAL_FILE_ENV}", "")
Expand Down Expand Up @@ -532,6 +547,7 @@ spec:
metadata:
labels:
app: {LABEL_APP}
{LABEL_IDENTITY}
spec:
serviceAccount: {SERVICE_ACCOUNT}
containers:
Expand Down Expand Up @@ -2648,3 +2664,10 @@ func constructServiceAccountSecrets(serviceAccountSecrets []string) string {

return serviceAccountSecretsData
}

func constructServiceAccountAnnotation(cloudIdentity string) string {
serviceAccountAnnotationData := "annotations:\n"
serviceAccountAnnotationData += fmt.Sprintf(" %s", cloudIdentity)

return serviceAccountAnnotationData
}
14 changes: 9 additions & 5 deletions cli/k8s_client/yaml_factory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ func TestYAMLFactory(t *testing.T) {
imagePullSecrets := []string{"thisisasecret"}

version := versionutils.MustParseSemantic("1.21.0")
cloudIdentity := AzureCloudIdentityKey + " a8rry78r8-7733-49bd-6656582"

deploymentArgs := &DeploymentYAMLArguments{
DeploymentName: Name,
Expand All @@ -93,11 +94,12 @@ func TestYAMLFactory(t *testing.T) {
TopologyEnabled: false,
HTTPRequestTimeout: config.HTTPTimeoutString,
EnableACP: true,
IdentityLabel: true,
}

yamlsOutputs := []string{
GetServiceAccountYAML(Name, nil, nil, nil),
GetServiceAccountYAML(Name, Secrets, labels, ownerRef),
GetServiceAccountYAML(Name, nil, nil, nil, ""),
GetServiceAccountYAML(Name, Secrets, labels, ownerRef, cloudIdentity),
GetRoleYAML(Namespace, Name, labels, ownerRef),
GetRoleBindingYAML(Namespace, Name, labels, ownerRef),
GetClusterRoleYAML(Name, nil, nil),
Expand Down Expand Up @@ -159,7 +161,9 @@ func TestValidateGetCSIDeploymentYAMLSuccess(t *testing.T) {
AutosupportCustomURL: "http://172.16.150.125:8888/",
ImageRegistry: "registry.k8s.io",
LogFormat: "text",
LogLevel: "debug",
LogLevel: "",
Debug: true,
AutosupportInsecure: true,
ImagePullSecrets: imagePullSecrets,
Labels: labels,
ControllingCRDetails: map[string]string{},
Expand Down Expand Up @@ -475,7 +479,7 @@ func TestGetCSIDaemonSetYAMLLinux(t *testing.T) {

for _, versionString := range versions {
version := versionutils.MustParseSemantic(versionString)
daemonsetArgs := &DaemonsetYAMLArguments{Version: version}
daemonsetArgs := &DaemonsetYAMLArguments{Version: version, LogLevel: "", Debug: true}

yamlData := GetCSIDaemonSetYAMLLinux(daemonsetArgs)
_, err := yaml.YAMLToJSON([]byte(yamlData))
Expand Down Expand Up @@ -824,7 +828,7 @@ func TestGetCSIDaemonSetYAMLWindowsImagePullPolicy(t *testing.T) {
for _, args := range testArgs {
for _, versionString := range versions {
version := versionutils.MustParseSemantic(versionString)
daemonsetArgs := &DaemonsetYAMLArguments{Version: version, ImagePullPolicy: args.imagePullPolicy}
daemonsetArgs := &DaemonsetYAMLArguments{Version: version, ImagePullPolicy: args.imagePullPolicy, LogLevel: "", Debug: true}

yamlData := GetCSIDaemonSetYAMLWindows(daemonsetArgs)
_, err := yaml.YAMLToJSON([]byte(yamlData))
Expand Down
1 change: 1 addition & 0 deletions helm/trident-operator/templates/tridentorchestrator.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,6 @@ spec:
imagePullPolicy: {{ include "imagePullPolicy" $ }}
windows: {{ .Values.windows }}
cloudProvider: {{ .Values.cloudProvider }}
cloudIdentity: {{ .Values.cloudIdentity }}
enableACP: {{ .Values.enableACP }}
acpImage: {{ .Values.acpImage }}
3 changes: 3 additions & 0 deletions helm/trident-operator/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,9 @@ excludePodSecurityPolicy: false
# cloudProvider indicates which cloud platform Trident is running on.
cloudProvider: ""

# cloudIdentity indicates the identity that needs to be set on service account.
cloudIdentity: ""

# enableACP allows enabling the Trident-ACP container to run.
enableACP: false

Expand Down
1 change: 1 addition & 0 deletions operator/controllers/orchestrator/apis/netapp/v1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ type TridentOrchestratorSpec struct {
Windows bool `json:"windows,omitempty"`
ImagePullPolicy string `json:"imagePullPolicy,omitempty"`
CloudProvider string `json:"cloudProvider,omitempty"`
CloudIdentity string `json:"cloudIdentity,omitempty"`
EnableACP bool `json:"enableACP,omitempty"`
ACPImage string `json:"acpImage,omitempty"`
}
Expand Down
Loading

0 comments on commit e8a7ca1

Please sign in to comment.