Skip to content
This repository has been archived by the owner on Jun 19, 2022. It is now read-only.

Add k8s service account under identity spec #1128

Merged
merged 5 commits into from
May 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
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
16 changes: 11 additions & 5 deletions config/core/resources/channel.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,14 @@ spec:
type: string
description: >
GCP service account used to poll the Cloud Pub/Sub Subscription. The value of the service
account must be a valid Google service account (see
account must be a valid Google service account. (see
https://cloud.google.com/iam/docs/service-accounts).
serviceAccountName:
grac3gao-zz marked this conversation as resolved.
Show resolved Hide resolved
type: string
description: >
k8s service account used to bind to a google service account to poll the Cloud Pub/Sub Subscription.
grac3gao-zz marked this conversation as resolved.
Show resolved Hide resolved
The value of the k8s service account must be a valid DNS subdomain name.
(see https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names)
secret:
type: object
description: >
Expand Down Expand Up @@ -209,12 +215,12 @@ spec:
spec:
type: object
properties:
googleServiceAccount:
serviceAccountName:
type: string
description: >
GCP service account used to poll the Cloud Pub/Sub Subscription. The value of the service
account must be a valid Google service account (see
https://cloud.google.com/iam/docs/service-accounts).
Kubernetes service account used to bind to a google service account to poll the Cloud Pub/Sub Subscription.
The value of the Kubernetes service account must be a valid DNS subdomain name.
(see https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names)
secret:
type: object
description: >
Expand Down
8 changes: 7 additions & 1 deletion config/core/resources/cloudauditlogssource.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,14 @@ spec:
type: string
description: >
GCP service account used to poll the Cloud Pub/Sub Subscription. The value of the service
account must be a valid Google service account (see
account must be a valid Google service account. (see
https://cloud.google.com/iam/docs/service-accounts).
serviceAccountName:
type: string
description: >
Kubernetes service account used to bind to a google service account to poll the Cloud Pub/Sub Subscription.
The value of the Kubernetes service account must be a valid DNS subdomain name.
(see https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names)
secret:
type: object
description: >
Expand Down
8 changes: 7 additions & 1 deletion config/core/resources/cloudbuildsource.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,14 @@ spec:
type: string
description: >
GCP service account used to poll the Cloud Pub/Sub Subscription. The value of the service
account must be a valid Google service account (see
account must be a valid Google service account. (see
https://cloud.google.com/iam/docs/service-accounts).
serviceAccountName:
type: string
description: >
Kubernetes service account used to bind to a google service account to poll the Cloud Pub/Sub Subscription.
The value of the Kubernetes service account must be a valid DNS subdomain name.
(see https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names)
secret:
type: object
description: >
Expand Down
8 changes: 7 additions & 1 deletion config/core/resources/cloudpubsubsource.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,14 @@ spec:
type: string
description: >
GCP service account used to poll the Cloud Pub/Sub Subscription. The value of the service
account must be a valid Google service account (see
account must be a valid Google service account. (see
https://cloud.google.com/iam/docs/service-accounts).
serviceAccountName:
type: string
description: >
Kubernetes service account used to bind to a google service account to poll the Cloud Pub/Sub Subscription.
The value of the Kubernetes service account must be a valid DNS subdomain name.
(see https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names)
secret:
type: object
description: >
Expand Down
8 changes: 7 additions & 1 deletion config/core/resources/cloudschedulersource.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,14 @@ spec:
type: string
description: >
GCP service account used to poll the Cloud Pub/Sub Subscription. The value of the service
account must be a valid Google service account (see
account must be a valid Google service account. (see
https://cloud.google.com/iam/docs/service-accounts).
serviceAccountName:
type: string
description: >
Kubernetes service account used to bind to a google service account to poll the Cloud Pub/Sub Subscription.
The value of the Kubernetes service account must be a valid DNS subdomain name.
(see https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names)
secret:
type: object
description: >
Expand Down
12 changes: 7 additions & 5 deletions config/core/resources/cloudstoragesource.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,14 @@ spec:
type: string
description: >
GCP service account used to poll the Cloud Pub/Sub Subscription. The value of the service
account must be a valid Google service account (see
account must be a valid Google service account. (see
https://cloud.google.com/iam/docs/service-accounts).
serviceAccountName:
type: string
description: >
Kubernetes service account used to bind to a google service account to poll the Cloud Pub/Sub Subscription.
The value of the Kubernetes service account must be a valid DNS subdomain name.
(see https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names)
secret:
type: object
description: >
Expand All @@ -137,10 +143,6 @@ spec:
description: >
Google Cloud Project ID of the project into which the topic should be created. If omitted uses
the Project ID from the GKE cluster metadata service.
serviceAccountName:
type: string
description: >
Service Account to run Receive Adapter as. If omitted, uses 'default'.
bucket:
type: string
description: >
Expand Down
3 changes: 3 additions & 0 deletions config/core/resources/old_pullsubscription.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ spec:
googleServiceAccount:
type: string
description: "GCP service account used to poll the Cloud Pub/Sub Subscription. The value of the service account must be a valid Google service account (see https://cloud.google.com/iam/docs/service-accounts)."
serviceAccountName:
type: string
description: "Kubernetes service account used to bind to a google service account to poll the Cloud Pub/Sub Subscription. The value of the Kubernetes service account must be a valid DNS subdomain name. (see https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names)"
secret:
type: object
description: "Credential used to poll the Cloud Pub/Sub Subscription. It is not used to create or delete the Subscription, only to poll it. The value of the secret entry must be a service account key in the JSON format (see https://cloud.google.com/iam/docs/creating-managing-service-account-keys). Defaults to secret.name of 'google-cloud-key' and secret.key of 'key.json'."
Expand Down
3 changes: 3 additions & 0 deletions config/core/resources/old_topic.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ spec:
googleServiceAccount:
type: string
description: "GCP service account used to poll the Cloud Pub/Sub Subscription. The value of the service account must be a valid Google service account (see https://cloud.google.com/iam/docs/service-accounts)."
serviceAccountName:
type: string
description: "Kubernetes service account used to bind to a google service account to poll the Cloud Pub/Sub Subscription. The value of the Kubernetes service account must be a valid DNS subdomain name. (see https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names)"
secret:
type: object
description: "Credential used to poll the Cloud Pub/Sub Subscription. It is not used to create or delete the Subscription, only to poll it. The value of the secret entry must be a service account key in the JSON format (see https://cloud.google.com/iam/docs/creating-managing-service-account-keys). Defaults to secret.name of 'google-cloud-key' and secret.key of 'key.json'."
Expand Down
3 changes: 3 additions & 0 deletions config/core/resources/pullsubscription.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ spec:
googleServiceAccount:
type: string
description: "GCP service account used to poll the Cloud Pub/Sub Subscription. The value of the service account must be a valid Google service account (see https://cloud.google.com/iam/docs/service-accounts)."
serviceAccountName:
type: string
description: "Kubernetes service account used to bind to a google service account to poll the Cloud Pub/Sub Subscription. The value of the Kubernetes service account must be a valid DNS subdomain name. (see https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names)"
secret:
type: object
description: "Credential used to poll the Cloud Pub/Sub Subscription. It is not used to create or delete the Subscription, only to poll it. The value of the secret entry must be a service account key in the JSON format (see https://cloud.google.com/iam/docs/creating-managing-service-account-keys). Defaults to secret.name of 'google-cloud-key' and secret.key of 'key.json'."
Expand Down
3 changes: 3 additions & 0 deletions config/core/resources/topic.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ spec:
googleServiceAccount:
type: string
description: "GCP service account used to poll the Cloud Pub/Sub Subscription. The value of the service account must be a valid Google service account (see https://cloud.google.com/iam/docs/service-accounts)."
serviceAccountName:
type: string
description: "Kubernetes service account used to bind to a google service account to poll the Cloud Pub/Sub Subscription. The value of the Kubernetes service account must be a valid DNS subdomain name. (see https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names)"
secret:
type: object
description: "Credential used to poll the Cloud Pub/Sub Subscription. It is not used to create or delete the Subscription, only to poll it. The value of the secret entry must be a service account key in the JSON format (see https://cloud.google.com/iam/docs/creating-managing-service-account-keys). Defaults to secret.name of 'google-cloud-key' and secret.key of 'key.json'."
Expand Down
4 changes: 2 additions & 2 deletions pkg/apis/convert/conversion_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,12 @@ func FromV1beta1PubSubSpec(from duckv1beta1.PubSubSpec) duckv1alpha1.PubSubSpec

func ToV1beta1IdentitySpec(from duckv1alpha1.IdentitySpec) duckv1beta1.IdentitySpec {
to := duckv1beta1.IdentitySpec{}
to.GoogleServiceAccount = from.GoogleServiceAccount
to.ServiceAccountName = from.ServiceAccountName
return to
}
func FromV1beta1IdentitySpec(from duckv1beta1.IdentitySpec) duckv1alpha1.IdentitySpec {
to := duckv1alpha1.IdentitySpec{}
to.GoogleServiceAccount = from.GoogleServiceAccount
to.ServiceAccountName = from.ServiceAccountName
return to
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/apis/convert/conversion_helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ var (
}

completeIdentitySpec = duckv1alpha1.IdentitySpec{
GoogleServiceAccount: "googleServiceAccount",
ServiceAccountName: "k8sServiceAccount",
}

completeSecret = &v1.SecretKeySelector{
Expand Down
38 changes: 30 additions & 8 deletions pkg/apis/duck/v1alpha1/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ const (
)

var (
validation_regexp = regexp.MustCompile(`^[a-z][a-z0-9-]{5,29}@[a-z][a-z0-9-]{5,29}.iam.gserviceaccount.com$`)
validation_regexp = regexp.MustCompile(`^[a-z][a-z0-9-]{5,29}@[a-z][a-z0-9-]{5,29}.iam.gserviceaccount.com$`)
validation_regexp_k8s = regexp.MustCompile(`^[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?$`)
)

// DefaultGoogleCloudSecretSelector is the default secret selector used to load
Expand All @@ -45,19 +46,25 @@ func DefaultGoogleCloudSecretSelector() *corev1.SecretKeySelector {
}
}

// ValidateCredential checks secret and GCP service account.
func ValidateCredential(secret *corev1.SecretKeySelector, gServiceAccountName string) *apis.FieldError {
if secret != nil && !equality.Semantic.DeepEqual(secret, &corev1.SecretKeySelector{}) && gServiceAccountName != "" {
// ValidateCredential checks secret and service account.
func ValidateCredential(secret *corev1.SecretKeySelector, gServiceAccountName string, kServiceAccountName string) *apis.FieldError {
if secret != nil && !equality.Semantic.DeepEqual(secret, &corev1.SecretKeySelector{}) && kServiceAccountName != "" {
return &apis.FieldError{
Message: "Can't have spec.googleServiceAccount and spec.secret at the same time",
Message: "Can't have spec.serviceAccountName and spec.secret at the same time",
Paths: []string{""},
}
} else if secret != nil && !equality.Semantic.DeepEqual(secret, &corev1.SecretKeySelector{}) {
return validateSecret(secret)
} else if gServiceAccountName != "" {
return validateGCPServiceAccount(gServiceAccountName)
} else {
var errs *apis.FieldError
if kServiceAccountName != "" {
errs = errs.Also(validateK8sServiceAccount(kServiceAccountName))
}
if gServiceAccountName != "" {
errs = errs.Also(validateGCPServiceAccount(gServiceAccountName))
}
return errs
}
return nil
}

func validateSecret(secret *corev1.SecretKeySelector) *apis.FieldError {
Expand Down Expand Up @@ -89,3 +96,18 @@ func validateGCPServiceAccount(gServiceAccountName string) *apis.FieldError {
}
return nil
}

func validateK8sServiceAccount(kServiceAccountName string) *apis.FieldError {
// The name of a k8s ServiceAccount object must be a valid DNS subdomain name.
// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names

match := validation_regexp_k8s.FindStringSubmatch(kServiceAccountName)
if len(match) == 0 {
return &apis.FieldError{
Message: fmt.Sprintf(`invalid value: %s, serviceAccountName should have format: ^[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?$`,
kServiceAccountName),
Paths: []string{"serviceAccountName"},
}
}
return nil
}
10 changes: 5 additions & 5 deletions pkg/apis/duck/v1alpha1/credentials_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,26 +59,26 @@ func TestValidateCredential(t *testing.T) {
serviceAccount: "",
wantErr: true,
}, {
name: "nil secret, and valid service account",
name: "nil secret, and valid k8s service account",
secret: nil,
serviceAccount: "test123@test123.iam.gserviceaccount.com",
serviceAccount: "test123",
wantErr: false,
}, {
name: "nil secret, and invalid service account",
secret: nil,
serviceAccount: "test@test",
serviceAccount: "@test",
wantErr: true,
}, {
name: "secret and service account exist at the same time",
secret: DefaultGoogleCloudSecretSelector(),
serviceAccount: "test@test.iam.gserviceaccount.com",
serviceAccount: "test",
wantErr: true,
}}

defer logtesting.ClearAll()

for _, tc := range testCases {
errs := ValidateCredential(tc.secret, tc.serviceAccount)
errs := ValidateCredential(tc.secret, "", tc.serviceAccount)
got := errs != nil
if diff := cmp.Diff(tc.wantErr, got); diff != "" {
t.Errorf("unexpected resource (-want, +got) = %v", diff)
Expand Down
5 changes: 5 additions & 0 deletions pkg/apis/duck/v1alpha1/identity_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ type IdentitySpec struct {
// If not specified, defaults to use secret.
// +optional
GoogleServiceAccount string `json:"googleServiceAccount,omitempty"`
// ServiceAccountName is the k8s service account which binds to a google service account.
// This google service account has required permissions to poll from a Cloud Pub/Sub subscription.
// If not specified, defaults to use secret.
// +optional
ServiceAccountName string `json:"serviceAccountName,omitempty"`
}

// IdentityStatus inherits duck/v1 Status and adds a ServiceAccountName.
Expand Down
3 changes: 2 additions & 1 deletion pkg/apis/duck/v1alpha1/pubsub_defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ import (
)

func (s *PubSubSpec) SetPubSubDefaults() {
if s.GoogleServiceAccount == "" && (s.Secret == nil || equality.Semantic.DeepEqual(s.Secret, &corev1.SecretKeySelector{})) {
if s.GoogleServiceAccount == "" && s.ServiceAccountName == "" &&
(s.Secret == nil || equality.Semantic.DeepEqual(s.Secret, &corev1.SecretKeySelector{})) {
s.Secret = DefaultGoogleCloudSecretSelector()
}
}
30 changes: 13 additions & 17 deletions pkg/apis/duck/v1beta1/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const (
)

var (
validation_regexp = regexp.MustCompile(`^[a-z][a-z0-9-]{5,29}@[a-z][a-z0-9-]{5,29}.iam.gserviceaccount.com$`)
validation_regexp_k8s = regexp.MustCompile(`^[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?$`)
)

// DefaultGoogleCloudSecretSelector is the default secret selector used to load
Expand All @@ -46,16 +46,16 @@ func DefaultGoogleCloudSecretSelector() *corev1.SecretKeySelector {
}

// ValidateCredential checks secret and GCP service account.
func ValidateCredential(secret *corev1.SecretKeySelector, gServiceAccountName string) *apis.FieldError {
if secret != nil && !equality.Semantic.DeepEqual(secret, &corev1.SecretKeySelector{}) && gServiceAccountName != "" {
func ValidateCredential(secret *corev1.SecretKeySelector, kServiceAccountName string) *apis.FieldError {
if secret != nil && !equality.Semantic.DeepEqual(secret, &corev1.SecretKeySelector{}) && kServiceAccountName != "" {
return &apis.FieldError{
Message: "Can't have spec.googleServiceAccount and spec.secret at the same time",
Message: "Can't have spec.serviceAccountName and spec.secret at the same time",
Paths: []string{""},
}
} else if secret != nil && !equality.Semantic.DeepEqual(secret, &corev1.SecretKeySelector{}) {
return validateSecret(secret)
} else if gServiceAccountName != "" {
return validateGCPServiceAccount(gServiceAccountName)
} else if kServiceAccountName != "" {
return validateK8sServiceAccount(kServiceAccountName)
}
return nil
}
Expand All @@ -71,20 +71,16 @@ func validateSecret(secret *corev1.SecretKeySelector) *apis.FieldError {
return errs
}

func validateGCPServiceAccount(gServiceAccountName string) *apis.FieldError {
// The format of gServiceAccountName is service-account-name@project-id.iam.gserviceaccount.com
func validateK8sServiceAccount(kServiceAccountName string) *apis.FieldError {
// The name of a k8s ServiceAccount object must be a valid DNS subdomain name.
// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names

// Service account name must be between 6 and 30 characters (inclusive),
// must begin with a lowercase letter, and consist of lowercase alphanumeric characters that can be separated by hyphens.

// Project IDs must start with a lowercase letter and can have lowercase ASCII letters, digits or hyphens,
// must be between 6 and 30 characters.
match := validation_regexp.FindStringSubmatch(gServiceAccountName)
match := validation_regexp_k8s.FindStringSubmatch(kServiceAccountName)
if len(match) == 0 {
return &apis.FieldError{
Message: fmt.Sprintf(`invalid value: %s, googleServiceAccount should have format: ^[a-z][a-z0-9-]{5,29}@[a-z][a-z0-9-]{5,29}.iam.gserviceaccount.com$`,
gServiceAccountName),
Paths: []string{"googleServiceAccount"},
Message: fmt.Sprintf(`invalid value: %s, serviceAccountName should have format: ^[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?$`,
kServiceAccountName),
Paths: []string{"serviceAccountName"},
}
}
return nil
Expand Down
Loading