diff --git a/cmd/flux/create_secret_proxy.go b/cmd/flux/create_secret_proxy.go new file mode 100644 index 0000000000..b0b3607f6a --- /dev/null +++ b/cmd/flux/create_secret_proxy.go @@ -0,0 +1,112 @@ +/* +Copyright 2024 The Flux authors + +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 main + +import ( + "context" + "errors" + + "github.com/spf13/cobra" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/yaml" + + "github.com/fluxcd/flux2/v2/internal/utils" + "github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret" +) + +var createSecretProxyCmd = &cobra.Command{ + Use: "proxy [name]", + Short: "Create or update a Kubernetes secret for proxy authentication", + Long: `The create secret proxy command generates a Kubernetes secret with the +proxy address and the basic authentication credentials.`, + Example: ` # Create a proxy secret on disk and encrypt it with SOPS + flux create secret proxy my-proxy \ + --namespace=my-namespace \ + --address=https://my-proxy.com \ + --username=my-username \ + --password=my-password \ + --export > proxy.yaml + + sops --encrypt --encrypted-regex '^(data|stringData)$' \ + --in-place proxy.yaml`, + + RunE: createSecretProxyCmdRun, +} + +type secretProxyFlags struct { + address string + username string + password string +} + +var secretProxyArgs secretProxyFlags + +func init() { + createSecretProxyCmd.Flags().StringVar(&secretProxyArgs.address, "address", "", "proxy address") + createSecretProxyCmd.Flags().StringVarP(&secretProxyArgs.username, "username", "u", "", "basic authentication username") + createSecretProxyCmd.Flags().StringVarP(&secretProxyArgs.password, "password", "p", "", "basic authentication password") + + createSecretCmd.AddCommand(createSecretProxyCmd) +} + +func createSecretProxyCmdRun(cmd *cobra.Command, args []string) error { + name := args[0] + + labels, err := parseLabels() + if err != nil { + return err + } + + if secretProxyArgs.address == "" { + return errors.New("address is required") + } + + opts := sourcesecret.Options{ + Name: name, + Namespace: *kubeconfigArgs.Namespace, + Labels: labels, + Address: secretProxyArgs.address, + Username: secretProxyArgs.username, + Password: secretProxyArgs.password, + } + secret, err := sourcesecret.Generate(opts) + if err != nil { + return err + } + + if createArgs.export { + rootCmd.Println(secret.Content) + return nil + } + + ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) + defer cancel() + kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions) + if err != nil { + return err + } + var s corev1.Secret + if err := yaml.Unmarshal([]byte(secret.Content), &s); err != nil { + return err + } + if err := upsertSecret(ctx, kubeClient, s); err != nil { + return err + } + + logger.Actionf("proxy secret '%s' created in '%s' namespace", name, *kubeconfigArgs.Namespace) + return nil +} diff --git a/cmd/flux/create_secret_proxy_test.go b/cmd/flux/create_secret_proxy_test.go new file mode 100644 index 0000000000..61f0043a8b --- /dev/null +++ b/cmd/flux/create_secret_proxy_test.go @@ -0,0 +1,47 @@ +/* +Copyright 2024 The Flux authors + +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 main + +import ( + "testing" +) + +func TestCreateProxySecret(t *testing.T) { + tests := []struct { + name string + args string + assert assertFunc + }{ + { + args: "create secret proxy proxy-secret", + assert: assertError("address is required"), + }, + { + args: "create secret proxy proxy-secret --address=https://my-proxy.com --username=my-username --password=my-password --namespace=my-namespace --export", + assert: assertGoldenFile("testdata/create_secret/proxy/secret-proxy.yaml"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := cmdTestCase{ + args: tt.args, + assert: tt.assert, + } + cmd.runTestCmd(t) + }) + } +} diff --git a/cmd/flux/main_test.go b/cmd/flux/main_test.go index b13bb6ce73..8d09f93882 100644 --- a/cmd/flux/main_test.go +++ b/cmd/flux/main_test.go @@ -453,6 +453,7 @@ func resetCmdArgs() { rhrArgs = reconcileHelmReleaseFlags{} rksArgs = reconcileKsFlags{} secretGitArgs = NewSecretGitFlags() + secretProxyArgs = secretProxyFlags{} secretHelmArgs = secretHelmFlags{} secretTLSArgs = secretTLSFlags{} sourceBucketArgs = sourceBucketFlags{} diff --git a/cmd/flux/testdata/create_secret/proxy/secret-proxy.yaml b/cmd/flux/testdata/create_secret/proxy/secret-proxy.yaml new file mode 100644 index 0000000000..3e5bed400a --- /dev/null +++ b/cmd/flux/testdata/create_secret/proxy/secret-proxy.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: proxy-secret + namespace: my-namespace +stringData: + address: https://my-proxy.com + password: my-password + username: my-username + diff --git a/pkg/manifestgen/sourcesecret/options.go b/pkg/manifestgen/sourcesecret/options.go index 166ebb699b..214a111467 100644 --- a/pkg/manifestgen/sourcesecret/options.go +++ b/pkg/manifestgen/sourcesecret/options.go @@ -31,6 +31,7 @@ const ( ) const ( + AddressSecretKey = "address" UsernameSecretKey = "username" PasswordSecretKey = "password" CACrtSecretKey = "ca.crt" @@ -73,6 +74,7 @@ type Options struct { BearerToken string VerificationCrts []VerificationCrt TrustPolicy []byte + Address string // Deprecated: Replaced by CACrt, but kept for backwards compatibility // with deprecated TLS flags. diff --git a/pkg/manifestgen/sourcesecret/sourcesecret.go b/pkg/manifestgen/sourcesecret/sourcesecret.go index 380115ca3e..d33ed9be17 100644 --- a/pkg/manifestgen/sourcesecret/sourcesecret.go +++ b/pkg/manifestgen/sourcesecret/sourcesecret.go @@ -148,6 +148,10 @@ func buildSecret(keypair *ssh.KeyPair, hostKey, dockerCfg []byte, options Option return } + if options.Address != "" { + secret.StringData[AddressSecretKey] = options.Address + } + if options.Username != "" && options.Password != "" { secret.StringData[UsernameSecretKey] = options.Username secret.StringData[PasswordSecretKey] = options.Password