Skip to content

Commit

Permalink
Add k8s service account under identity spec (google#1128)
Browse files Browse the repository at this point in the history
* add k8s service account

* change

* add intevents

* add channel

* change channel
  • Loading branch information
grac3gao-zz authored and ian-mi committed May 27, 2020
1 parent 2263b3c commit ee73af1
Show file tree
Hide file tree
Showing 82 changed files with 384 additions and 274 deletions.
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:
type: string
description: >
k8s service account used to bind to a google service account to poll the Cloud Pub/Sub Subscription.
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

0 comments on commit ee73af1

Please sign in to comment.