diff --git a/cyclops-ctrl/api/v1alpha1/template_auth_rule_types.go b/cyclops-ctrl/api/v1alpha1/template_auth_rule_types.go index 61b48f5ca..0a1cff2e9 100644 --- a/cyclops-ctrl/api/v1alpha1/template_auth_rule_types.go +++ b/cyclops-ctrl/api/v1alpha1/template_auth_rule_types.go @@ -31,8 +31,9 @@ type TemplateAuthRuleSpec struct { Repo string `json:"repo"` - Username v1.SecretKeySelector `json:"username"` - Password v1.SecretKeySelector `json:"password"` + Username v1.SecretKeySelector `json:"username"` + Password v1.SecretKeySelector `json:"password"` + CABundle *v1.SecretKeySelector `json:"ca-bundle,omitempty"` } //+kubebuilder:object:root=true diff --git a/cyclops-ctrl/api/v1alpha1/zz_generated.deepcopy.go b/cyclops-ctrl/api/v1alpha1/zz_generated.deepcopy.go index 0bce7172a..4b0a8a400 100644 --- a/cyclops-ctrl/api/v1alpha1/zz_generated.deepcopy.go +++ b/cyclops-ctrl/api/v1alpha1/zz_generated.deepcopy.go @@ -21,6 +21,7 @@ limitations under the License. package v1alpha1 import ( + "k8s.io/api/core/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -273,6 +274,11 @@ func (in *TemplateAuthRuleSpec) DeepCopyInto(out *TemplateAuthRuleSpec) { *out = *in in.Username.DeepCopyInto(&out.Username) in.Password.DeepCopyInto(&out.Password) + if in.CABundle != nil { + in, out := &in.CABundle, &out.CABundle + *out = new(v1.SecretKeySelector) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TemplateAuthRuleSpec. diff --git a/cyclops-ctrl/config/crd/bases/cyclops-ui.com_templateauthrules.yaml b/cyclops-ctrl/config/crd/bases/cyclops-ui.com_templateauthrules.yaml index 293e73e79..9df23b74e 100644 --- a/cyclops-ctrl/config/crd/bases/cyclops-ui.com_templateauthrules.yaml +++ b/cyclops-ctrl/config/crd/bases/cyclops-ui.com_templateauthrules.yaml @@ -39,6 +39,31 @@ spec: spec: description: TemplateAuthRuleSpec defines the desired state of TemplateAuthRule properties: + ca-bundle: + description: SecretKeySelector selects a key of a Secret. + properties: + key: + description: The key of the secret to select from. Must be a + valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. + type: string + optional: + description: Specify whether the Secret or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic password: description: SecretKeySelector selects a key of a Secret. properties: diff --git a/cyclops-ctrl/internal/auth/templates.go b/cyclops-ctrl/internal/auth/templates.go index 03cf30ac7..0d5daac86 100644 --- a/cyclops-ctrl/internal/auth/templates.go +++ b/cyclops-ctrl/internal/auth/templates.go @@ -13,6 +13,7 @@ type TemplatesResolver struct { type Credentials struct { Username string Password string + CABundle []byte } func NewTemplatesResolver(k8s k8sClient) TemplatesResolver { @@ -44,9 +45,18 @@ func (t TemplatesResolver) RepoAuthCredentials(repo string) (*Credentials, error return nil, err } + var caBundle []byte + if ta.Spec.CABundle != nil { + caBundle, err = t.k8s.GetTemplateAuthRuleSecret(ta.Spec.CABundle.Name, ta.Spec.CABundle.Key) + if err != nil { + return nil, err + } + } + return &Credentials{ - Username: username, - Password: password, + Username: string(username), + Password: string(password), + CABundle: caBundle, }, err } } @@ -55,6 +65,6 @@ func (t TemplatesResolver) RepoAuthCredentials(repo string) (*Credentials, error } type k8sClient interface { - GetTemplateAuthRuleSecret(string, string) (string, error) + GetTemplateAuthRuleSecret(string, string) ([]byte, error) ListTemplateAuthRules() ([]v1alpha1.TemplateAuthRule, error) } diff --git a/cyclops-ctrl/internal/auth/templates_test.go b/cyclops-ctrl/internal/auth/templates_test.go index 4a71138cd..6f3a929c4 100644 --- a/cyclops-ctrl/internal/auth/templates_test.go +++ b/cyclops-ctrl/internal/auth/templates_test.go @@ -129,7 +129,7 @@ var _ = Describe("Templates resolver", func() { repo: "https://github.com/my-org/my-team", mockCalls: func() { k8sClient.On("ListTemplateAuthRules").Return(tars, nil) - k8sClient.On("GetTemplateAuthRuleSecret", "secret-name", "username").Return("", errors.New("some k8s error")) + k8sClient.On("GetTemplateAuthRuleSecret", "secret-name", "username").Return(nil, errors.New("some k8s error")) }, }, out: caseOutput{ @@ -143,8 +143,8 @@ var _ = Describe("Templates resolver", func() { repo: "https://github.com/my-org/my-team", mockCalls: func() { k8sClient.On("ListTemplateAuthRules").Return(tars, nil) - k8sClient.On("GetTemplateAuthRuleSecret", "secret-name", "username").Return("my-secret-username", nil) - k8sClient.On("GetTemplateAuthRuleSecret", "secret-name", "token").Return("", errors.New("some k8s error")) + k8sClient.On("GetTemplateAuthRuleSecret", "secret-name", "username").Return([]byte("my-secret-username"), nil) + k8sClient.On("GetTemplateAuthRuleSecret", "secret-name", "token").Return(nil, errors.New("some k8s error")) }, }, out: caseOutput{ @@ -158,8 +158,8 @@ var _ = Describe("Templates resolver", func() { repo: "https://github.com/my-org/my-team", mockCalls: func() { k8sClient.On("ListTemplateAuthRules").Return(tars, nil) - k8sClient.On("GetTemplateAuthRuleSecret", "secret-name", "username").Return("my-secret-username", nil) - k8sClient.On("GetTemplateAuthRuleSecret", "secret-name", "token").Return("my-secret-token", nil) + k8sClient.On("GetTemplateAuthRuleSecret", "secret-name", "username").Return([]byte("my-secret-username"), nil) + k8sClient.On("GetTemplateAuthRuleSecret", "secret-name", "token").Return([]byte("my-secret-token"), nil) }, }, out: caseOutput{ diff --git a/cyclops-ctrl/internal/template/git.go b/cyclops-ctrl/internal/template/git.go index 4f14b0f2a..d9626be3e 100644 --- a/cyclops-ctrl/internal/template/git.go +++ b/cyclops-ctrl/internal/template/git.go @@ -287,6 +287,7 @@ func resolveRef(repo, version string, creds *auth.Credentials) (string, error) { refs, err := rem.List(&git.ListOptions{ PeelingOption: git.AppendPeeled, Auth: httpBasicAuthCredentials(creds), + CABundle: gitCABundle(creds), }) if err != nil { return "", errors.Wrap(err, fmt.Sprintf("repo %s was not cloned successfully; authentication might be required; check if repository exists and you referenced it correctly", repo)) @@ -312,6 +313,7 @@ func resolveDefaultBranchRef(repo string, creds *auth.Credentials) (string, erro refs, err := rem.List(&git.ListOptions{ PeelingOption: git.AppendPeeled, Auth: httpBasicAuthCredentials(creds), + CABundle: gitCABundle(creds), }) if err != nil { return "", errors.Wrap(err, fmt.Sprintf("repo %s was not cloned successfully; authentication might be required; check if repository exists and you referenced it correctly", repo)) @@ -359,9 +361,10 @@ func clone(repoURL, commit string, creds *auth.Credentials) (billy.Filesystem, e } repo, err := git.Clone(memory.NewStorage(), memfs.New(), &git.CloneOptions{ - URL: repoURL, - Tags: git.AllTags, - Auth: httpBasicAuthCredentials(creds), + URL: repoURL, + Tags: git.AllTags, + Auth: httpBasicAuthCredentials(creds), + CABundle: gitCABundle(creds), }) if err != nil { return nil, errors.Wrap(err, fmt.Sprintf("repo %s was not cloned successfully; authentication might be required; check if repository exists and you referenced it correctly", repoURL)) @@ -381,7 +384,8 @@ func clone(repoURL, commit string, creds *auth.Credentials) (billy.Filesystem, e return nil, err } refList, err := remote.List(&git.ListOptions{ - Auth: httpBasicAuthCredentials(creds), + Auth: httpBasicAuthCredentials(creds), + CABundle: gitCABundle(creds), }) if err != nil { return nil, err @@ -568,3 +572,11 @@ func httpBasicAuthCredentials(creds *auth.Credentials) *http.BasicAuth { Password: creds.Password, } } + +func gitCABundle(creds *auth.Credentials) []byte { + if creds == nil { + return nil + } + + return creds.CABundle +} diff --git a/cyclops-ctrl/pkg/cluster/k8sclient/client.go b/cyclops-ctrl/pkg/cluster/k8sclient/client.go index 2ebb005ba..2a48c6ce5 100644 --- a/cyclops-ctrl/pkg/cluster/k8sclient/client.go +++ b/cyclops-ctrl/pkg/cluster/k8sclient/client.go @@ -101,7 +101,7 @@ type IKubernetesClient interface { WatchResource(group, version, resource, name, namespace string) (watch.Interface, error) WatchKubernetesResources(gvrs []ResourceWatchSpec, stopCh chan struct{}) (chan *unstructured.Unstructured, error) ListTemplateAuthRules() ([]cyclopsv1alpha1.TemplateAuthRule, error) - GetTemplateAuthRuleSecret(name, key string) (string, error) + GetTemplateAuthRuleSecret(string, string) ([]byte, error) ListTemplateStore() ([]cyclopsv1alpha1.TemplateStore, error) CreateTemplateStore(ts *cyclopsv1alpha1.TemplateStore) error UpdateTemplateStore(ts *cyclopsv1alpha1.TemplateStore) error diff --git a/cyclops-ctrl/pkg/cluster/k8sclient/templateauthrules.go b/cyclops-ctrl/pkg/cluster/k8sclient/templateauthrules.go index c43c9bafd..609d564a2 100644 --- a/cyclops-ctrl/pkg/cluster/k8sclient/templateauthrules.go +++ b/cyclops-ctrl/pkg/cluster/k8sclient/templateauthrules.go @@ -13,16 +13,16 @@ func (k *KubernetesClient) ListTemplateAuthRules() ([]cyclopsv1alpha1.TemplateAu return k.moduleset.TemplateAuthRules(k.moduleNamespace).List(metav1.ListOptions{}) } -func (k *KubernetesClient) GetTemplateAuthRuleSecret(name, key string) (string, error) { +func (k *KubernetesClient) GetTemplateAuthRuleSecret(name, key string) ([]byte, error) { secret, err := k.clientset.CoreV1().Secrets(k.moduleNamespace).Get(context.Background(), name, metav1.GetOptions{}) if err != nil { - return "", err + return nil, err } secretValue, ok := secret.Data[key] if !ok { - return "", errors.New("key not found") + return nil, errors.New("key not found") } - return string(secretValue), err + return secretValue, err } diff --git a/cyclops-ctrl/pkg/mocks/IKubernetesClient.go b/cyclops-ctrl/pkg/mocks/IKubernetesClient.go index 3b4d16903..39ad5b3f9 100644 --- a/cyclops-ctrl/pkg/mocks/IKubernetesClient.go +++ b/cyclops-ctrl/pkg/mocks/IKubernetesClient.go @@ -1227,27 +1227,29 @@ func (_c *IKubernetesClient_GetStreamedPodLogs_Call) RunAndReturn(run func(conte return _c } -// GetTemplateAuthRuleSecret provides a mock function with given fields: name, key -func (_m *IKubernetesClient) GetTemplateAuthRuleSecret(name string, key string) (string, error) { - ret := _m.Called(name, key) +// GetTemplateAuthRuleSecret provides a mock function with given fields: _a0, _a1 +func (_m *IKubernetesClient) GetTemplateAuthRuleSecret(_a0 string, _a1 string) ([]byte, error) { + ret := _m.Called(_a0, _a1) if len(ret) == 0 { panic("no return value specified for GetTemplateAuthRuleSecret") } - var r0 string + var r0 []byte var r1 error - if rf, ok := ret.Get(0).(func(string, string) (string, error)); ok { - return rf(name, key) + if rf, ok := ret.Get(0).(func(string, string) ([]byte, error)); ok { + return rf(_a0, _a1) } - if rf, ok := ret.Get(0).(func(string, string) string); ok { - r0 = rf(name, key) + if rf, ok := ret.Get(0).(func(string, string) []byte); ok { + r0 = rf(_a0, _a1) } else { - r0 = ret.Get(0).(string) + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } } if rf, ok := ret.Get(1).(func(string, string) error); ok { - r1 = rf(name, key) + r1 = rf(_a0, _a1) } else { r1 = ret.Error(1) } @@ -1261,25 +1263,25 @@ type IKubernetesClient_GetTemplateAuthRuleSecret_Call struct { } // GetTemplateAuthRuleSecret is a helper method to define mock.On call -// - name string -// - key string -func (_e *IKubernetesClient_Expecter) GetTemplateAuthRuleSecret(name interface{}, key interface{}) *IKubernetesClient_GetTemplateAuthRuleSecret_Call { - return &IKubernetesClient_GetTemplateAuthRuleSecret_Call{Call: _e.mock.On("GetTemplateAuthRuleSecret", name, key)} +// - _a0 string +// - _a1 string +func (_e *IKubernetesClient_Expecter) GetTemplateAuthRuleSecret(_a0 interface{}, _a1 interface{}) *IKubernetesClient_GetTemplateAuthRuleSecret_Call { + return &IKubernetesClient_GetTemplateAuthRuleSecret_Call{Call: _e.mock.On("GetTemplateAuthRuleSecret", _a0, _a1)} } -func (_c *IKubernetesClient_GetTemplateAuthRuleSecret_Call) Run(run func(name string, key string)) *IKubernetesClient_GetTemplateAuthRuleSecret_Call { +func (_c *IKubernetesClient_GetTemplateAuthRuleSecret_Call) Run(run func(_a0 string, _a1 string)) *IKubernetesClient_GetTemplateAuthRuleSecret_Call { _c.Call.Run(func(args mock.Arguments) { run(args[0].(string), args[1].(string)) }) return _c } -func (_c *IKubernetesClient_GetTemplateAuthRuleSecret_Call) Return(_a0 string, _a1 error) *IKubernetesClient_GetTemplateAuthRuleSecret_Call { +func (_c *IKubernetesClient_GetTemplateAuthRuleSecret_Call) Return(_a0 []byte, _a1 error) *IKubernetesClient_GetTemplateAuthRuleSecret_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *IKubernetesClient_GetTemplateAuthRuleSecret_Call) RunAndReturn(run func(string, string) (string, error)) *IKubernetesClient_GetTemplateAuthRuleSecret_Call { +func (_c *IKubernetesClient_GetTemplateAuthRuleSecret_Call) RunAndReturn(run func(string, string) ([]byte, error)) *IKubernetesClient_GetTemplateAuthRuleSecret_Call { _c.Call.Return(run) return _c } diff --git a/install/cyclops-install.yaml b/install/cyclops-install.yaml index 87d026dca..3cc821c18 100644 --- a/install/cyclops-install.yaml +++ b/install/cyclops-install.yaml @@ -186,6 +186,31 @@ spec: spec: description: TemplateAuthRuleSpec defines the desired state of TemplateAuthRule properties: + ca-bundle: + description: SecretKeySelector selects a key of a Secret. + properties: + key: + description: The key of the secret to select from. Must be a + valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. + type: string + optional: + description: Specify whether the Secret or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic password: description: SecretKeySelector selects a key of a Secret. properties: @@ -383,7 +408,7 @@ spec: spec: containers: - name: cyclops-ui - image: cyclopsui/cyclops-ui:v0.16.1 + image: cyclopsui/cyclops-ui:v0.18.0-rc.2 ports: - containerPort: 80 env: @@ -448,7 +473,7 @@ spec: serviceAccountName: cyclops-ctrl containers: - name: cyclops-ctrl - image: cyclopsui/cyclops-ctrl:v0.16.1 + image: cyclopsui/cyclops-ctrl:v0.18.0-rc.2 ports: - containerPort: 8080 env: