diff --git a/apis/generators/v1alpha1/generator_github.go b/apis/generators/v1alpha1/generator_github.go new file mode 100644 index 00000000000..ed228791859 --- /dev/null +++ b/apis/generators/v1alpha1/generator_github.go @@ -0,0 +1,59 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + esmeta "github.com/external-secrets/external-secrets/apis/meta/v1" +) + +type GithubAccessTokenSpec struct { + // URL configures the Github instance URL. Defaults to /~https://github.com/. + URL string `json:"url,omitempty"` + AppID string `json:"appID"` + InstallID string `json:"installID"` + // Auth configures how ESO authenticates with a Github instance. + Auth GithubAuth `json:"auth"` +} + +type GithubAuth struct { + PrivatKey GithubSecretRef `json:"privatKey"` +} + +type GithubSecretRef struct { + SecretRef esmeta.SecretKeySelector `json:"secretRef"` +} + +// GithubAccessToken generates ghs_ accessToken +// +kubebuilder:object:root=true +// +kubebuilder:storageversion +// +kubebuilder:subresource:status +// +kubebuilder:resource:scope=Namespaced,categories={githubaccesstoken},shortName=githubaccesstoken +type GithubAccessToken struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec GithubAccessTokenSpec `json:"spec,omitempty"` +} + +// +kubebuilder:object:root=true + +// GithubAccessToken contains a list of ExternalSecret resources. +type GithubAccessTokenList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []GithubAccessToken `json:"items"` +} diff --git a/apis/generators/v1alpha1/register.go b/apis/generators/v1alpha1/register.go index 875a3bb02b1..f3896fe359d 100644 --- a/apis/generators/v1alpha1/register.go +++ b/apis/generators/v1alpha1/register.go @@ -92,9 +92,18 @@ var ( VaultDynamicSecretGroupVersionKind = SchemeGroupVersion.WithKind(VaultDynamicSecretKind) ) +// GithubAccessToken type metadata. +var ( + GithubAccessTokenKind = reflect.TypeOf(GithubAccessToken{}).Name() + GithubAccessTokenGroupKind = schema.GroupKind{Group: Group, Kind: GithubAccessTokenKind}.String() + GithubAccessTokenKindAPIVersion = GithubAccessTokenKind + "." + SchemeGroupVersion.String() + GithubAccessTokenGroupVersionKind = SchemeGroupVersion.WithKind(GithubAccessTokenKind) +) + func init() { SchemeBuilder.Register(&ECRAuthorizationToken{}, &ECRAuthorizationToken{}) SchemeBuilder.Register(&GCRAccessToken{}, &GCRAccessTokenList{}) + SchemeBuilder.Register(&GithubAccessToken{}, &GithubAccessTokenList{}) SchemeBuilder.Register(&ACRAccessToken{}, &ACRAccessTokenList{}) SchemeBuilder.Register(&Fake{}, &FakeList{}) SchemeBuilder.Register(&VaultDynamicSecret{}, &VaultDynamicSecretList{}) diff --git a/apis/generators/v1alpha1/zz_generated.deepcopy.go b/apis/generators/v1alpha1/zz_generated.deepcopy.go index fccf797205f..b71166d4baf 100644 --- a/apis/generators/v1alpha1/zz_generated.deepcopy.go +++ b/apis/generators/v1alpha1/zz_generated.deepcopy.go @@ -566,6 +566,112 @@ func (in *GCRAccessTokenSpec) DeepCopy() *GCRAccessTokenSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GithubAccessToken) DeepCopyInto(out *GithubAccessToken) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GithubAccessToken. +func (in *GithubAccessToken) DeepCopy() *GithubAccessToken { + if in == nil { + return nil + } + out := new(GithubAccessToken) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GithubAccessToken) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GithubAccessTokenList) DeepCopyInto(out *GithubAccessTokenList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]GithubAccessToken, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GithubAccessTokenList. +func (in *GithubAccessTokenList) DeepCopy() *GithubAccessTokenList { + if in == nil { + return nil + } + out := new(GithubAccessTokenList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GithubAccessTokenList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GithubAccessTokenSpec) DeepCopyInto(out *GithubAccessTokenSpec) { + *out = *in + in.Auth.DeepCopyInto(&out.Auth) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GithubAccessTokenSpec. +func (in *GithubAccessTokenSpec) DeepCopy() *GithubAccessTokenSpec { + if in == nil { + return nil + } + out := new(GithubAccessTokenSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GithubAuth) DeepCopyInto(out *GithubAuth) { + *out = *in + in.PrivatKey.DeepCopyInto(&out.PrivatKey) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GithubAuth. +func (in *GithubAuth) DeepCopy() *GithubAuth { + if in == nil { + return nil + } + out := new(GithubAuth) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GithubSecretRef) DeepCopyInto(out *GithubSecretRef) { + *out = *in + in.SecretRef.DeepCopyInto(&out.SecretRef) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GithubSecretRef. +func (in *GithubSecretRef) DeepCopy() *GithubSecretRef { + if in == nil { + return nil + } + out := new(GithubSecretRef) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Password) DeepCopyInto(out *Password) { *out = *in diff --git a/config/crds/bases/generators.external-secrets.io_githubaccesstokens.yaml b/config/crds/bases/generators.external-secrets.io_githubaccesstokens.yaml new file mode 100644 index 00000000000..36c6867daa2 --- /dev/null +++ b/config/crds/bases/generators.external-secrets.io_githubaccesstokens.yaml @@ -0,0 +1,91 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + name: githubaccesstokens.generators.external-secrets.io +spec: + group: generators.external-secrets.io + names: + categories: + - githubaccesstoken + kind: GithubAccessToken + listKind: GithubAccessTokenList + plural: githubaccesstokens + shortNames: + - githubaccesstoken + singular: githubaccesstoken + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: GithubAccessToken generates ghs_ accessToken + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + properties: + appID: + type: string + auth: + description: Auth configures how ESO authenticates with a Github instance. + properties: + privatKey: + properties: + secretRef: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + properties: + key: + description: |- + The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be + defaulted, in others it may be required. + type: string + name: + description: The name of the Secret resource being referred + to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - secretRef + type: object + required: + - privatKey + type: object + installID: + type: string + url: + description: URL configures the Github instance URL. Defaults to /~https://github.com/. + type: string + required: + - appID + - auth + - installID + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/deploy/charts/external-secrets/templates/rbac.yaml b/deploy/charts/external-secrets/templates/rbac.yaml index 1bf5a229693..46053d92a5f 100644 --- a/deploy/charts/external-secrets/templates/rbac.yaml +++ b/deploy/charts/external-secrets/templates/rbac.yaml @@ -53,6 +53,7 @@ rules: - "ecrauthorizationtokens" - "fakes" - "gcraccesstokens" + - "githubaccesstokens" - "passwords" - "vaultdynamicsecrets" verbs: @@ -145,6 +146,7 @@ rules: - "ecrauthorizationtokens" - "fakes" - "gcraccesstokens" + - "githubaccesstokens" - "passwords" - "vaultdynamicsecrets" verbs: @@ -188,6 +190,7 @@ rules: - "ecrauthorizationtokens" - "fakes" - "gcraccesstokens" + - "githubaccesstokens" - "passwords" - "vaultdynamicsecrets" verbs: diff --git a/deploy/crds/bundle.yaml b/deploy/crds/bundle.yaml index 06c63ab8bde..d7b4945f057 100644 --- a/deploy/crds/bundle.yaml +++ b/deploy/crds/bundle.yaml @@ -10512,6 +10512,107 @@ spec: --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + name: githubaccesstokens.generators.external-secrets.io +spec: + group: generators.external-secrets.io + names: + categories: + - githubaccesstoken + kind: GithubAccessToken + listKind: GithubAccessTokenList + plural: githubaccesstokens + shortNames: + - githubaccesstoken + singular: githubaccesstoken + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: GithubAccessToken generates ghs_ accessToken + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + properties: + appID: + type: string + auth: + description: Auth configures how ESO authenticates with a Github instance. + properties: + privatKey: + properties: + secretRef: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + properties: + key: + description: |- + The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be + defaulted, in others it may be required. + type: string + name: + description: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - secretRef + type: object + required: + - privatKey + type: object + installID: + type: string + url: + description: URL configures the Github instance URL. Defaults to /~https://github.com/. + type: string + required: + - appID + - auth + - installID + type: object + type: object + served: true + storage: true + subresources: + status: {} + conversion: + strategy: Webhook + webhook: + conversionReviewVersions: + - v1 + clientConfig: + service: + name: kubernetes + namespace: default + path: /convert +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.14.0 diff --git a/docs/snippets/generator-github-example.yaml b/docs/snippets/generator-github-example.yaml new file mode 100644 index 00000000000..d409ea6bc50 --- /dev/null +++ b/docs/snippets/generator-github-example.yaml @@ -0,0 +1,14 @@ +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: github-auth-token +spec: + refreshInterval: "30m" + target: + name: github-auth-token # Name for the secret to be created on the cluster + dataFrom: + - sourceRef: + generatorRef: + apiVersion: generators.external-secrets.io/v1alpha1 + kind: GithubAccessToken + name: github-auth-token diff --git a/docs/snippets/generator-github.yaml b/docs/snippets/generator-github.yaml new file mode 100644 index 00000000000..4a1f00c00fd --- /dev/null +++ b/docs/snippets/generator-github.yaml @@ -0,0 +1,20 @@ +# 1. Register Github app https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/registering-a-github-app#registering-a-github-app +# `App ID: 123456` will be displayed after you create an app. Next on the bottom of the page, you'll find `Generate a private key` button. +# 2. Get privateKey https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/managing-private-keys-for-github-apps#generating-private-keys put it in e.g `github-app-pem` k8s secret +# 3. Set permissions for the app, e.g if you want to push OCI images to ghr set RW for packages https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/choosing-permissions-for-a-github-app#choosing-permissions-for-rest-api-access +# 4. Install your Github app https://docs.github.com/en/apps/using-github-apps/installing-your-own-github-app +# 5. Get `installID` https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-an-installation-access-token-for-a-github-app#generating-an-installation-access-token (2) +--- +apiVersion: generators.external-secrets.io/v1alpha1 +kind: GithubAccessToken +metadata: + name: github-auth-token +spec: + appID: "0000000" # (1) + installID: "00000000" # (5) + url: "" # (Default https://api.github.com.) + auth: + privatKey: + secretRef: + name: github-app-pem # (2) + key: key diff --git a/docs/snippets/getallsecrets-find-by-name.yaml b/docs/snippets/getallsecrets-find-by-name.yaml index 7421cd338ff..f34a4ed419f 100644 --- a/docs/snippets/getallsecrets-find-by-name.yaml +++ b/docs/snippets/getallsecrets-find-by-name.yaml @@ -1,4 +1,4 @@ -apiVersion: external-secrets.io/v1beta1 +apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: find-by-tags @@ -12,4 +12,4 @@ spec: dataFrom: - find: name: - regexp: "key" \ No newline at end of file + regexp: "key" diff --git a/pkg/generator/github/github.go b/pkg/generator/github/github.go new file mode 100644 index 00000000000..bc9146fc372 --- /dev/null +++ b/pkg/generator/github/github.go @@ -0,0 +1,169 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package github + +import ( + "context" + "crypto/rsa" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/golang-jwt/jwt/v5" + corev1 "k8s.io/api/core/v1" + apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/yaml" + + genv1alpha1 "github.com/external-secrets/external-secrets/apis/generators/v1alpha1" +) + +type Generator struct { + httpClient *http.Client +} + +type Github struct { + HTTP *http.Client + Kube client.Client + Namespace string + URL string + InstallTkn string +} + +const ( + defaultLoginUsername = "token" + defaultGithubAPI = "https://api.github.com" + + errNoSpec = "no config spec provided" + errParseSpec = "unable to parse spec: %w" + errGetToken = "unable to get authorization token: %w" + + contextTimeout = 30 * time.Second + httpClientTimeout = 5 * time.Second +) + +func (g *Generator) Generate(ctx context.Context, jsonSpec *apiextensions.JSON, kube client.Client, namespace string) (map[string][]byte, error) { + return g.generate( + ctx, + jsonSpec, + kube, + namespace, + ) +} + +func (g *Generator) generate( + ctx context.Context, + jsonSpec *apiextensions.JSON, + kube client.Client, + namespace string) (map[string][]byte, error) { + if jsonSpec == nil { + return nil, fmt.Errorf(errNoSpec) + } + ctx, cancel := context.WithTimeout(ctx, contextTimeout) + defer cancel() + + gh, err := newGHClient(ctx, kube, namespace, g.httpClient, jsonSpec) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + // Github api expects POST request + req, err := http.NewRequestWithContext(ctx, http.MethodPost, gh.URL, http.NoBody) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + req.Header.Add("Authorization", "Bearer "+gh.InstallTkn) + req.Header.Add("Accept", "application/vnd.github.v3+json") + + resp, err := gh.HTTP.Do(req) + if err != nil { + return nil, fmt.Errorf("error performing request: %w", err) + } + defer resp.Body.Close() + + // git access token + var gat map[string]interface{} + if err := json.NewDecoder(resp.Body).Decode(&gat); err != nil && resp.StatusCode >= 200 && resp.StatusCode < 300 { + return nil, fmt.Errorf("error decoding response: %w", err) + } + + accessToken, ok := gat["token"].(string) + if !ok { + return nil, fmt.Errorf("token isn't a string or token key doesn't exist") + } + return map[string][]byte{ + defaultLoginUsername: []byte(accessToken), + }, nil +} + +func newGHClient(ctx context.Context, k client.Client, n string, hc *http.Client, + js *apiextensions.JSON) (*Github, error) { + if hc == nil { + hc = &http.Client{ + Timeout: httpClientTimeout, + } + } + res, err := parseSpec(js.Raw) + if err != nil { + return nil, fmt.Errorf(errParseSpec, err) + } + gh := &Github{Kube: k, Namespace: n, HTTP: hc} + + ghPath := fmt.Sprintf("/app/installations/%s/access_tokens", res.Spec.InstallID) + gh.URL = defaultGithubAPI + ghPath + if res.Spec.URL != "" { + gh.URL = res.Spec.URL + ghPath + } + secret := &corev1.Secret{} + if err := gh.Kube.Get(ctx, client.ObjectKey{Name: res.Spec.Auth.PrivatKey.SecretRef.Name, Namespace: n}, secret); err != nil { + return nil, fmt.Errorf("error getting GH pem from secret:%w", err) + } + + pk, err := jwt.ParseRSAPrivateKeyFromPEM(secret.Data[res.Spec.Auth.PrivatKey.SecretRef.Key]) + if err != nil { + return nil, fmt.Errorf("error parsing RSA private key: %w", err) + } + if gh.InstallTkn, err = GetInstallationToken(pk, res.Spec.AppID); err != nil { + return nil, fmt.Errorf("can't get InstallationToken: %w", err) + } + return gh, nil +} + +// Get github installation token. +func GetInstallationToken(key *rsa.PrivateKey, aid string) (string, error) { + claims := jwt.RegisteredClaims{ + Issuer: aid, + IssuedAt: jwt.NewNumericDate(time.Now().Add(-time.Second * 10)), + ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Second * 300)), + } + + token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) + signedToken, err := token.SignedString(key) + if err != nil { + return "", fmt.Errorf("error signing token: %w", err) + } + + return signedToken, nil +} + +func parseSpec(data []byte) (*genv1alpha1.GithubAccessToken, error) { + var spec genv1alpha1.GithubAccessToken + err := yaml.Unmarshal(data, &spec) + return &spec, err +} + +func init() { + genv1alpha1.Register(genv1alpha1.GithubAccessTokenKind, &Generator{}) +} diff --git a/pkg/generator/github/github_test.go b/pkg/generator/github/github_test.go new file mode 100644 index 00000000000..665c2b8629d --- /dev/null +++ b/pkg/generator/github/github_test.go @@ -0,0 +1,138 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package github + +import ( + "context" + "fmt" + "net/http" + "net/http/httptest" + "os" + "reflect" + "testing" + + "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" + apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +const ( + tstCrtName = "github_test.pem" +) + +func testHTTPSrv(t *testing.T, r []byte) *httptest.Server { + return httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + assert.Equal(t, "POST", req.Method, "Expected POST request") + assert.Empty(t, req.Body) + assert.NotEmpty(t, req.Header.Get("Authorization")) + assert.Equal(t, "application/vnd.github.v3+json", req.Header.Get("Accept")) + + // Send response to be tested + rw.Write(r) + })) +} +func TestGenerate(t *testing.T) { + type args struct { + ctx context.Context + jsonSpec *apiextensions.JSON + kube client.Client + namespace string + } + pem, err := os.ReadFile(tstCrtName) + assert.NoError(t, err, "Should not error when reading privatKey") + + validResponce := []byte(`{ + "token": "ghs_16C7e42F292c6912E7710c838347Ae178B4a", + "expires_at": "2016-07-11T22:14:10Z", + "permissions": { + "issues": "write", + "contents": "read" + }, + "repository_selection": "selected" + }`) + + server := testHTTPSrv(t, validResponce) + + tests := []struct { + name string + g *Generator + args args + want map[string][]byte + wantErr bool + }{ + { + name: "nil spec", + args: args{ + jsonSpec: nil, + }, + wantErr: true, + }, + { + name: "full spec", + args: args{ + ctx: context.TODO(), + namespace: "foo", + kube: clientfake.NewClientBuilder().WithObjects(&v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testName", + Namespace: "foo", + }, + Data: map[string][]byte{ + "privatKey": pem, + }, + }).Build(), + jsonSpec: &apiextensions.JSON{ + Raw: []byte(fmt.Sprintf(`apiVersion: generators.external-secrets.io/v1alpha1 +kind: GithubToken +spec: + appID: "0000000" + installID: "00000000" + URL: %q + auth: + privatKey: + secretRef: + name: "testName" + namespace: "foo" + key: "privatKey"`, server.URL)), + }, + }, + want: map[string][]byte{ + "token": []byte("ghs_16C7e42F292c6912E7710c838347Ae178B4a"), + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := &Generator{httpClient: server.Client()} + got, err := g.generate( + tt.args.ctx, + tt.args.jsonSpec, + tt.args.kube, + tt.args.namespace, + ) + if (err != nil) != tt.wantErr { + t.Errorf("Generator.Generate() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Generator.Generate() = %s, want %s", got, tt.want) + } + }) + } +} diff --git a/pkg/generator/github/github_test.pem b/pkg/generator/github/github_test.pem new file mode 100644 index 00000000000..5c00cc498f2 --- /dev/null +++ b/pkg/generator/github/github_test.pem @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIJKQIBAAKCAgEAsgzs6vN2sveHVraXV0zdoVyhWUHWNQ0xnhHTPhjt5ggHmSvr +UxvUpXfKWCP9gZo59Q7dx0ydjqBsdooXComVP4kGDjulvOHWgvcVmwTsL0bAMqms +CyyJKM6JWqi8E+CPTOpMBWdapUxvwaSmop8geiTtnX0aV4zGXwsz2mwdogbounQj +MB/Ew7vv8XtqwXSpnR7kM5HPfM7wb9F8MjlRuna6Nt2V7i0oUr+EEt6fIYEVZFiH +TSUzDLaz2eClJeCNdvyqaeGCCqs+LunMq3kZjO9ahtS2+1qZxfBzac/0KXRYnLa0 +kGQHZbw0ecgdZC9YpqqMeTeSnJPPX4/TQt54qVLQXM3+h8xvwt3lItcJPZR0v+0y +Qe5QEwPL4c5UF81jfGrYfEzmGth6KRImRMdFLF9+F7ozAgGqCLQt3eV2YMXIBYfZ +S9L/lO/Q3m4MGARZXUE3jlkcfFlcbnA0uwMBSjdNUsw4zHjVwk6aG5CwYFYVHG9n +5v4qCxKVENRinzgGRnwkNyADecvbcQ30/UOuhU5YBnfFSYrrhq/fyCbpneuxk2Eo +uL3pk/GA7mGzqhjPYzaaNGVZ8n+Yys0kxuP9XDOUEDkjXpa/SzeZEk9FXMlLc7Wy +dj/7ES4r6SYCs4KMr+p7CjFg/a7IdepLQ3txrZecrBxoG5mBDYgCJCfLBu0CAwEA +AQKCAgA1Vrvu0sq/aHnp1z9VTtiiS26mn5t9PxubH/npg2xZWhR0pXyU5CR7AXzj +lLyQA9TS/gYge2pD3PlBNbMbXAYTB4iB4QqQoBM0HrMhQoNC0m4nfz7kBg585Aqv +1xao2b/0KchmYgT8uf5Mw3eMBiGjlcZ9RIoMqkaPGHsLNxJVhL5ZhQs5knrOrFGA +RRnBJKLfR+7TKB5BZHkQ9m+/V/6M3p6AazdMJ8kJqQf24yxGzDXNXtwBl2BIsb8F +SVAQHcojWCPxHjZn3c7+HNpMkDXAS8AR3k2G1Sh17MeWbk7V0F3vbKiBDQZOSuhp +hzKO3cQwAa2dbrGEKJ+aICsIwD7i8sbvw3E7sWsEhJHrXuG51alrD2NpB1QiCVgv +a3ikF5SPbqtX4htlRYmzYwZM8jtB79yStORWKou0+v5SsCliT7xqU1exrygsVGdz +lWnYu8R/YIQoWEn6rC3CwhcwwHBBKeDjjaMMD7SYIIiC11vjANnKCobVcaPrpENS +Dycct8acc8SkP5XLTwcqSv66D/O2EU/+mnwJCpBqXa8SC7Bnku9WyncJBfuDFQQl +JFrV5uhxtzhfYRCE7UcsTRX82yrA0BIsV+SWnAQEh4zIvuEwSmPcF0mY518+/kpk +HSxGNrBwb1ja4+vsXHkUuNXOWG6BLiZ70yDOXZeZwYkCIgQSHQKCAQEA3P0ADDW+ +ZuDBBMMPscnwTakFnIaS7od2W6eJhKdnu10afW0rhbug1Y7w7gzLF3CtESWo/tWb +fl9ndsXAEtLpSZgFOFuMA+H9iQOsTMz6tx4zXhXA2jGt98fahYsWjdyFq7UhEijr +mQCr13FMc9KEfh/lEeSfBERdRnhCBpGAqYAXfdp/l19EIMWTofxa51q64LDjQ55u +nVTz2G8nr7HVp+rBKk2gnLFyweSBXkrLGxaLTCaxJEeFrBga2jv5WJGcXX4LXncu +1egUqsqmlzOepL6Q/W9QId9iWltcVTDW3wRuO9MkDURkqAP24RLFNXcOoAbI8ePR +R6PaINsQbYk+UwKCAQEAzkJsfYzD4rnyRYkwq0N9vQuwZQ7UKhtkvPnQWEcawTz2 ++fCYg6HEmM475mAstYaL3H4v1mGz4Fq9UTxIWcAiSPJdIJAHq5/i8Y4mruLzc14y +wPZRjTroK7j4okhHvXxENge2p8KV5tocLM0ZVX/uovgPbABGpyvaQkMI5povxSDa +OFZqvha/e5BqtpTovN9+RAEwFIyercf0SGFjLyuI9GULEWwfqo4OvdcnE8LdYKjW +CuRLahGajrt19bjbt15LCGRGd4kyFFYDTFy26GggLXDvqnUw7XTn6AU/4Gw3ORw5 +fxJf5ELF5wYy1erUOaH8LSRk1WoMgil2g6jZJE19vwKCAQEA0ToUrnq/36WR+hE4 +rcqU4uJRdsYPHRlSHSr9T4Qz+TgIGZKf70ka2LcyMyAXtQSwRxjR7RyO0NJBIjnO +RcQ8rbnpz1cVtKNlqTC6FCjKg09rsPuFkNASdxNYOLHcU8njIRQn0Iq/rSfuitcx +XEOHv+YwuoUrbR3Q9iRr1s4x88lb9INH5CiFV0XZJjfIVV0YrB2tvlqlPf6ttFBh +Ub5cnFPuOUAv/csf7KWNOpozvFzW2+2SL9grnilgWxkHVizez8HDv9e1lz7ZOm8N +1QBBhpcKrXiTdM6LzyLKw7mu5o3KVIfujUUgy9adCrH710f2p9pkrGhWv65Jmmvu +HNchEwKCAQBOfRJh2G42WgIqmeEuWvl/NfKDEliESXZVP08cOLqirDtjsz2mYam5 +aEl9Cj4ZOcEBP/eeQgG8L2t5fVIe7TFexvPPT1/L3IT03N41kOGJlmAD8/fmoXL2 +KGZdAtph7ebbFKZaQn7eoUM1fTrVwWAjHfhoZdZ9CP/+VRoO/r+M6UqBQ8lM2sU1 +FSi2oAXM0dNvt2//cd90S/HWlVC0A4ITVlwW3ilSsspDTZtuNqodfUIuVN+p1lcV +V5q0zgq2RaiR4e660DeBa5XHukRUPkN4Z1CccgoTYnhZX54GHcgJ8Iakp25cI1jB +6CbyJnFqGQ0odH/2gmuOII8b3OX8nYxrAoIBAQDFuMaBg7Xa0535v+6NY0iPgF5O +fKEQI9pGlLk8oKOZKLMRqQYba2qWE4jXjUyl0g3iQ1IYynFi3+cayDoMCrBXmbZ5 +mGebuBySHYpBv3ajhOf1JV1cl1xivgUxM5LW708kNOuf4/hTZXR3D34kJAhoxS+/ +KMkcE4BT8IZIHQ+wIMhmYLAdSQCVVv8x78jN0sZCC0fjqVuyPdYQ8sIc3OHsJZcW +lzewFW72lfsiB/RxWZ/XwXONXeW5Quf+XwbGGboTofyzTxzsYSwn1U9Kt8iaY8zr +z7Z5SQCSf2Js9V9lJcodYswWlxrdtoRKA/WgrvQkZhGGAePTUVoO5Lab29M8 +-----END RSA PRIVATE KEY----- diff --git a/pkg/generator/register/register.go b/pkg/generator/register/register.go index 5d06bc00a6f..aeb6886d161 100644 --- a/pkg/generator/register/register.go +++ b/pkg/generator/register/register.go @@ -22,6 +22,7 @@ import ( _ "github.com/external-secrets/external-secrets/pkg/generator/ecr" _ "github.com/external-secrets/external-secrets/pkg/generator/fake" _ "github.com/external-secrets/external-secrets/pkg/generator/gcr" + _ "github.com/external-secrets/external-secrets/pkg/generator/github" _ "github.com/external-secrets/external-secrets/pkg/generator/password" _ "github.com/external-secrets/external-secrets/pkg/generator/vault" _ "github.com/external-secrets/external-secrets/pkg/generator/webhook"