Skip to content

Commit

Permalink
Add check oidc-config command
Browse files Browse the repository at this point in the history
The command validates a given OIDC configuration, either from a referenced
Secret or from CLI flags. This will help users debug issues with Weave GitOps
OIDC configuration as well as provide a way to validate a configuration before
putting it on a cluster.

The command consumes OIDC configuration from CLI flags or from a Secret on a
cluster and sends the user through an OIDC authorization code flow. If it
succeeds, the username and groups claims are logged to stdout. The command
validates the Secret for missing fields and also prints all errors returned
from the OIDC provider so that users know what went wrong.

This will work out of the box with OIDC providers given they are configured to
accept "http://localhost:9876" as a redirect URI.
  • Loading branch information
Max Jonas Werner committed Nov 24, 2023
1 parent 6095bdb commit d1e9c95
Show file tree
Hide file tree
Showing 13 changed files with 1,156 additions and 21 deletions.
18 changes: 13 additions & 5 deletions cmd/gitops/check/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,27 @@ package check
import (
"fmt"

"github.com/weaveworks/weave-gitops/cmd/gitops/check/oidcconfig"
"github.com/weaveworks/weave-gitops/cmd/gitops/config"
"github.com/weaveworks/weave-gitops/pkg/services/check"

"github.com/spf13/cobra"
)

var Cmd = &cobra.Command{
Use: "check",
Short: "Validates flux compatibility",
Example: `
func GetCommand(opts *config.Options) *cobra.Command {
cmd := &cobra.Command{
Use: "check",
Short: "Validates flux compatibility",
Example: `
# Validate flux and kubernetes compatibility
gitops check
`,
RunE: runCmd,
RunE: runCmd,
}

cmd.AddCommand(oidcconfig.OIDCConfigCommand(opts))

return cmd
}

func runCmd(_ *cobra.Command, _ []string) error {
Expand Down
127 changes: 127 additions & 0 deletions cmd/gitops/check/oidcconfig/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package oidcconfig

import (
"context"
"fmt"
"os"
"strings"
"time"

"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"

"github.com/weaveworks/weave-gitops/cmd/gitops/cmderrors"
"github.com/weaveworks/weave-gitops/cmd/gitops/config"
"github.com/weaveworks/weave-gitops/pkg/logger"
"github.com/weaveworks/weave-gitops/pkg/oidc/check"
"github.com/weaveworks/weave-gitops/pkg/run"
"github.com/weaveworks/weave-gitops/pkg/server/auth"
)

func OIDCConfigCommand(opts *config.Options) *cobra.Command {
var (
kubeConfigArgs *genericclioptions.ConfigFlags
fromSecretFlag string
skipSecretFlag bool
clientIDFlag string
clientSecretFlag string
scopesFlag []string
claimUsernameFlag string
issuerURLFlag string
)

cmd := &cobra.Command{
Use: "oidc-config",
Short: "Check an OIDC configuration for proper functionality.",
Long: "This command will send the user through an OIDC authorization code flow " +
"using the given OIDC configuration. This is helpful for verifying that a given " +
"configuration will work properly with Weave GitOps or for debugging issues. " +
"Without any provided flags it will read the configuration from a Secret" +
"on the cluster.",
Example: `
# Check the OIDC configuration stored in the flux-system/oidc-auth Secret
gitops check oidc-config
# Check a different set of scopes
gitops check oidc-config --scopes=openid,groups
# Check a different username cliam
gitops check oidc-config --claim-username=sub
# Check configuration without fetching a Secret from the cluster
gitops check oidc-config --skip-secret --client-id=CID --client-secret=SEC --issuer-url=https://example.org
`,
SilenceUsage: true,
SilenceErrors: true,
RunE: func(cmd *cobra.Command, args []string) error {
if skipSecretFlag {
fromSecretFlag = "" // the skip flag overrides this one.
}

cfg, err := kubeConfigArgs.ToRESTConfig()
if err != nil {
return err
}

if fromSecretFlag == "" {
if clientIDFlag == "" ||
clientSecretFlag == "" ||
issuerURLFlag == "" {
return fmt.Errorf("when not reading OIDC configuration from a Secret, you need to provide " +
"client ID, client secret and issuer URL using the respective command-line flags")
}
}

log := logger.NewCLILogger(os.Stdout)
kubeClient, err := run.GetKubeClient(log, *kubeConfigArgs.Context, cfg, nil)
if err != nil {
return cmderrors.ErrGetKubeClient
}

ns, err := cmd.Flags().GetString("namespace")
if err != nil {
return fmt.Errorf("failed getting namespace flag: %w", err)
}

ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
defer cancel()
claims, err := check.GetClaims(ctx, check.Options{
ClientID: clientIDFlag,
ClientSecret: clientSecretFlag,
IssuerURL: issuerURLFlag,
SecretName: fromSecretFlag,
SecretNamespace: ns,
Scopes: scopesFlag,
ClaimUsername: claimUsernameFlag,
}, log, kubeClient)

if err != nil {
return fmt.Errorf("failed getting claims: %w", err)
}

log.Println("user: %s", claims.Username)
if len(claims.Groups) != 0 {
log.Println("groups: %s", strings.Join(claims.Groups, ", "))
} else {
log.Println("no groups claim")
}

return nil
},
DisableAutoGenTag: true,
}

kubeConfigArgs = run.GetKubeConfigArgs()
kubeConfigArgs.AddFlags(cmd.Flags())

cmd.Flags().StringVar(&clientIDFlag, "client-id", "", "OIDC client ID")
cmd.Flags().StringVar(&clientSecretFlag, "client-secret", "", "OIDC client secret")
cmd.Flags().StringVar(&issuerURLFlag, "issuer-url", "", "OIDC issuer URL")
cmd.Flags().StringVar(&fromSecretFlag, "from-secret", "oidc-auth", "Get OIDC configuration from the given Secret resource")
cmd.Flags().BoolVar(&skipSecretFlag, "skip-secret", false,
"Do not read OIDC configuration from a Kubernetes Secret but rely solely on the values from the given flags.")
cmd.Flags().StringVar(&claimUsernameFlag, "claim-username", "", "ID token claim to use as the user name.")
cmd.Flags().StringSliceVar(&scopesFlag, "scopes", nil, fmt.Sprintf("OIDC scopes to request (default [%s])", strings.Join(auth.DefaultScopes, ",")))

return cmd
}
2 changes: 1 addition & 1 deletion cmd/gitops/root/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ func RootCmd() *cobra.Command {
rootCmd.AddCommand(get.GetCommand(options))
rootCmd.AddCommand(set.SetCommand(options))
rootCmd.AddCommand(docs.Cmd)
rootCmd.AddCommand(check.Cmd)
rootCmd.AddCommand(check.GetCommand(options))
rootCmd.AddCommand(create.GetCommand(options))
rootCmd.AddCommand(deletepkg.GetCommand(options))
rootCmd.AddCommand(logs.GetCommand(options))
Expand Down
8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ require (
github.com/NYTimes/gziphandler v1.1.1
github.com/alexedwards/scs/v2 v2.5.1
github.com/cheshir/ttlcache v1.0.1-0.20220504185148-8ceeff21b789
github.com/coreos/go-oidc/v3 v3.1.0
github.com/coreos/go-oidc/v3 v3.4.0
github.com/fluxcd/go-git-providers v0.16.0
github.com/fluxcd/helm-controller/api v0.35.0
github.com/fluxcd/image-automation-controller/api v0.33.1
Expand Down Expand Up @@ -46,10 +46,10 @@ require (
github.com/yannh/kubeconform v0.5.0
go.uber.org/zap v1.25.0
golang.org/x/crypto v0.14.0
golang.org/x/oauth2 v0.7.0
golang.org/x/oauth2 v0.13.0
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1
google.golang.org/grpc v1.56.3
google.golang.org/protobuf v1.30.0
google.golang.org/protobuf v1.31.0
gopkg.in/square/go-jose.v2 v2.6.0
gopkg.in/yaml.v3 v3.0.1
gotest.tools v2.2.0+incompatible
Expand Down Expand Up @@ -196,7 +196,7 @@ require (
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.9.3 // indirect
gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/appengine v1.6.8 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
Expand Down
Loading

0 comments on commit d1e9c95

Please sign in to comment.