-
Notifications
You must be signed in to change notification settings - Fork 62
feat(cmd/auth) New command to support Workload Identity on GKE #288
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
7e6bc4a
79c3311
9fd3a3b
a9753c5
0ee01b3
ecf6e79
c44aa33
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// Copyright 2023 The Falco 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 gcp defines the logic to authenticate against an Artifact registry using GCP credentials. | ||
package gcp |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
// Copyright 2023 The Falco 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 gcp | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/spf13/cobra" | ||
|
||
"github.com/falcosecurity/falcoctl/internal/config" | ||
"github.com/falcosecurity/falcoctl/internal/login/gcp" | ||
"github.com/falcosecurity/falcoctl/pkg/options" | ||
) | ||
|
||
const ( | ||
longGcp = `Register a registry to use GCE Metadata server or gcloud Application Default credentials to connect to it. | ||
|
||
Example | ||
falcoctl registry gcp europe-docker.pkg.dev | ||
` | ||
) | ||
|
||
// RegistryGcpOptions contains the options for the registry gcp command. | ||
type RegistryGcpOptions struct { | ||
*options.CommonOptions | ||
} | ||
|
||
// NewGcpCmd returns the gcp command. | ||
func NewGcpCmd(ctx context.Context, opt *options.CommonOptions) *cobra.Command { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need to add the new command to the README.md: https://github.com/falcosecurity/falcoctl#falcoctl-registry. |
||
o := RegistryGcpOptions{ | ||
CommonOptions: opt, | ||
} | ||
|
||
cmd := &cobra.Command{ | ||
Use: "gcp [REGISTRY]", | ||
DisableFlagsInUseLine: true, | ||
Short: "Register an OCI registry to log in using GCP common credentials", | ||
Long: longGcp, | ||
Args: cobra.ExactArgs(1), | ||
SilenceErrors: true, | ||
SilenceUsage: true, | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
return o.RunGcp(ctx, args) | ||
}, | ||
} | ||
|
||
return cmd | ||
} | ||
|
||
// RunGcp executes the business logic for the gcp command. | ||
func (o *RegistryGcpOptions) RunGcp(ctx context.Context, args []string) error { | ||
var err error | ||
reg := args[0] | ||
if err = gcp.Login(ctx, reg); err != nil { | ||
return err | ||
} | ||
o.Printer.Success.Printfln("GCP authentication successful for %q", reg) | ||
|
||
o.Printer.Verbosef("Adding new gcp entry to configuration file %q", o.ConfigFile) | ||
if err = config.AddGcp([]config.GcpAuth{{ | ||
Registry: reg, | ||
}}, o.ConfigFile); err != nil { | ||
return fmt.Errorf("index entry %q: %w", reg, err) | ||
} | ||
|
||
o.Printer.Success.Printfln("GCP authentication entry for %q successfully added in configuration file", reg) | ||
|
||
return nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -78,6 +78,8 @@ const ( | |
RegistryAuthOauthKey = "registry.auth.oauth" | ||
// RegistryAuthBasicKey is the Viper key for basic authentication configuration. | ||
RegistryAuthBasicKey = "registry.auth.basic" | ||
// RegistryAuthGcpKey is the Viper key for gcp authentication configuration. | ||
RegistryAuthGcpKey = "registry.auth.gcp" | ||
// IndexesKey is the Viper key for indexes configuration. | ||
IndexesKey = "indexes" | ||
// ArtifactFollowEveryKey is the Viper key for follower "every" configuration. | ||
|
@@ -127,6 +129,11 @@ type BasicAuth struct { | |
Password string `mapstructure:"password"` | ||
} | ||
|
||
// GcpAuth represents a Gcp activation setting. | ||
type GcpAuth struct { | ||
Registry string `mapstructure:"registry"` | ||
} | ||
|
||
// Follow represents the follower configuration. | ||
type Follow struct { | ||
Every time.Duration `mapstructure:"every"` | ||
|
@@ -216,6 +223,17 @@ func Indexes() ([]Index, error) { | |
return indexes, nil | ||
} | ||
|
||
// Gcps retrieves the gcp auth section of the config file. | ||
func Gcps() ([]GcpAuth, error) { | ||
var auths []GcpAuth | ||
|
||
if err := viper.UnmarshalKey(RegistryAuthGcpKey, &auths, viper.DecodeHook(gcpAuthListHookFunc())); err != nil { | ||
return nil, fmt.Errorf("unable to get gcpAuths: %w", err) | ||
} | ||
|
||
return auths, nil | ||
} | ||
|
||
// indexListHookFunc returns a DecodeHookFunc that converts | ||
// strings to string slices, when the target type is DotSeparatedStringList. | ||
// when passed as env should be in the following format: | ||
|
@@ -393,6 +411,45 @@ func oathAuthListHookFunc() mapstructure.DecodeHookFuncType { | |
} | ||
} | ||
|
||
// oauthAuthListHookFunc returns a DecodeHookFunc that converts | ||
// strings to string slices, when the target type is DotSeparatedStringList. | ||
// when passed as env should be in the following format: | ||
// "registry;registry1". | ||
func gcpAuthListHookFunc() mapstructure.DecodeHookFuncType { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need to add the new ENV variable to the README.md: https://github.com/falcosecurity/falcoctl#falcoctl-environment-variables. |
||
return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { | ||
if f.Kind() != reflect.String && f.Kind() != reflect.Slice { | ||
return data, nil | ||
} | ||
|
||
if t != reflect.TypeOf([]GcpAuth{}) { | ||
return data, fmt.Errorf("unable to decode data since destination variable is not of type %T", []GcpAuth{}) | ||
} | ||
|
||
switch f.Kind() { | ||
case reflect.String: | ||
if !SemicolonSeparatedRegexp.MatchString(data.(string)) { | ||
return data, fmt.Errorf("env variable not correctly set, should match %q, got %q", SemicolonSeparatedRegexp.String(), data.(string)) | ||
} | ||
tokens := strings.Split(data.(string), ";") | ||
auths := make([]GcpAuth, len(tokens)) | ||
for i, token := range tokens { | ||
auths[i] = GcpAuth{ | ||
Registry: token, | ||
} | ||
} | ||
return auths, nil | ||
case reflect.Slice: | ||
var auths []GcpAuth | ||
if err := mapstructure.WeakDecode(data, &auths); err != nil { | ||
return err, nil | ||
} | ||
return auths, nil | ||
default: | ||
return nil, nil | ||
} | ||
} | ||
} | ||
|
||
// Follower retrieves the follower section of the config file. | ||
func Follower() (Follow, error) { | ||
// with Follow we can just use nested keys. | ||
|
@@ -550,3 +607,34 @@ func findIndexInSlice(slice []Index, val *Index) (int, bool) { | |
} | ||
return -1, false | ||
} | ||
|
||
// AddGcp appends the provided gcps to a configuration file if not present. | ||
func AddGcp(gcps []GcpAuth, configFile string) error { | ||
var currGcps []GcpAuth | ||
var err error | ||
|
||
// Retrieve the current gcps from configuration. | ||
if currGcps, err = Gcps(); err != nil { | ||
return err | ||
} | ||
for i, gcp := range gcps { | ||
if _, ok := findGcpInSlice(currGcps, &gcps[i]); !ok { | ||
currGcps = append(currGcps, gcp) | ||
} | ||
} | ||
|
||
if err := UpdateConfigFile(RegistryAuthGcpKey, currGcps, configFile); err != nil { | ||
return fmt.Errorf("unable to update gcps list in the config file %q: %w", configFile, err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func findGcpInSlice(slice []GcpAuth, val *GcpAuth) (int, bool) { | ||
for i, item := range slice { | ||
if item.Registry == val.Registry { | ||
return i, true | ||
} | ||
} | ||
return -1, false | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// Copyright 2023 The Falco 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 gcp implements gcp credentials login functionality. | ||
package gcp |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
// Copyright 2023 The Falco 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 gcp | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"golang.org/x/oauth2" | ||
"golang.org/x/oauth2/google" | ||
|
||
"github.com/falcosecurity/falcoctl/pkg/oci/registry" | ||
) | ||
|
||
// Login checks if passed gcp credentials are correct. | ||
func Login(ctx context.Context, reg string) error { | ||
// Check that we can find a valid token source using GCE or ApplicationDefault. | ||
ts, err := google.DefaultTokenSource(ctx) | ||
if err != nil { | ||
return fmt.Errorf("wrong GCP token source, unable to find a valid source: %w", err) | ||
} | ||
|
||
// Check that we can retrieve token. | ||
_, err = ts.Token() | ||
if err != nil { | ||
return fmt.Errorf("wrong GCP credentials, unable to retrieve token: %w", err) | ||
} | ||
|
||
// Check connection to the registry | ||
client := oauth2.NewClient(ctx, ts) | ||
|
||
r, err := registry.NewRegistry(reg, registry.WithClient(client)) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if err := r.CheckConnection(ctx); err != nil { | ||
return fmt.Errorf("unable to connect to registry %q: %w", reg, err) | ||
} | ||
|
||
return nil | ||
} |
Uh oh!
There was an error while loading. Please reload this page.