diff --git a/cmd/kops-controller/main.go b/cmd/kops-controller/main.go index 543f58ce6c..d1ba60b9e8 100644 --- a/cmd/kops-controller/main.go +++ b/cmd/kops-controller/main.go @@ -124,52 +124,67 @@ func main() { vfsContext := vfs.NewVFSContext() if opt.Server != nil { - var verifier bootstrap.Verifier + var verifiers []bootstrap.Verifier var err error if opt.Server.Provider.AWS != nil { - verifier, err = awsup.NewAWSVerifier(opt.Server.Provider.AWS) + verifier, err := awsup.NewAWSVerifier(opt.Server.Provider.AWS) if err != nil { setupLog.Error(err, "unable to create verifier") os.Exit(1) } - } else if opt.Server.Provider.GCE != nil { - verifier, err = gcetpmverifier.NewTPMVerifier(opt.Server.Provider.GCE) + verifiers = append(verifiers, verifier) + } + if opt.Server.Provider.GCE != nil { + verifier, err := gcetpmverifier.NewTPMVerifier(opt.Server.Provider.GCE) if err != nil { setupLog.Error(err, "unable to create verifier") os.Exit(1) } - } else if opt.Server.Provider.Hetzner != nil { - verifier, err = hetzner.NewHetznerVerifier(opt.Server.Provider.Hetzner) + verifiers = append(verifiers, verifier) + } + if opt.Server.Provider.Hetzner != nil { + verifier, err := hetzner.NewHetznerVerifier(opt.Server.Provider.Hetzner) if err != nil { setupLog.Error(err, "unable to create verifier") os.Exit(1) } - } else if opt.Server.Provider.OpenStack != nil { - verifier, err = openstack.NewOpenstackVerifier(opt.Server.Provider.OpenStack) + verifiers = append(verifiers, verifier) + } + if opt.Server.Provider.OpenStack != nil { + verifier, err := openstack.NewOpenstackVerifier(opt.Server.Provider.OpenStack) if err != nil { setupLog.Error(err, "unable to create verifier") os.Exit(1) } - } else if opt.Server.Provider.DigitalOcean != nil { - verifier, err = do.NewVerifier(ctx, opt.Server.Provider.DigitalOcean) + verifiers = append(verifiers, verifier) + } + if opt.Server.Provider.DigitalOcean != nil { + verifier, err := do.NewVerifier(ctx, opt.Server.Provider.DigitalOcean) if err != nil { setupLog.Error(err, "unable to create verifier") os.Exit(1) } - } else if opt.Server.Provider.Scaleway != nil { - verifier, err = scaleway.NewScalewayVerifier(ctx, opt.Server.Provider.Scaleway) + verifiers = append(verifiers, verifier) + } + if opt.Server.Provider.Scaleway != nil { + verifier, err := scaleway.NewScalewayVerifier(ctx, opt.Server.Provider.Scaleway) if err != nil { setupLog.Error(err, "unable to create verifier") os.Exit(1) } - } else if opt.Server.Provider.Azure != nil { - verifier, err = azure.NewAzureVerifier(ctx, opt.Server.Provider.Azure) + verifiers = append(verifiers, verifier) + } + if opt.Server.Provider.Azure != nil { + verifier, err := azure.NewAzureVerifier(ctx, opt.Server.Provider.Azure) if err != nil { setupLog.Error(err, "unable to create verifier") os.Exit(1) } - } else { - klog.Fatalf("server cloud provider config not provided") + verifiers = append(verifiers, verifier) + } + + if len(verifiers) == 0 { + klog.Fatalf("server verifiers not provided") } uncachedClient, err := client.New(mgr.GetConfig(), client.Options{ @@ -181,6 +196,8 @@ func main() { os.Exit(1) } + verifier := bootstrap.NewChainVerifier(verifiers...) + srv, err := server.NewServer(vfsContext, &opt, verifier, uncachedClient) if err != nil { setupLog.Error(err, "unable to create server") diff --git a/pkg/bootstrap/authenticate.go b/pkg/bootstrap/authenticate.go index 648cbaa8b9..dd04777394 100644 --- a/pkg/bootstrap/authenticate.go +++ b/pkg/bootstrap/authenticate.go @@ -49,5 +49,12 @@ type VerifyResult struct { // Verifier verifies authentication credentials for requests. type Verifier interface { + // VerifyToken performs full validation of the provided token, often making cloud API calls to verify the caller. + // It should return either an error or a validated VerifyResult. + // If the token looks like it is intended for a different verifier + // (for example it has the wrong prefix), we should return ErrNotThisVerifier VerifyToken(ctx context.Context, rawRequest *http.Request, token string, body []byte) (*VerifyResult, error) } + +// ErrNotThisVerifier is returned when a verifier receives a token that is not intended for it. +var ErrNotThisVerifier = errors.New("token not valid for this verifier") diff --git a/pkg/bootstrap/chain.go b/pkg/bootstrap/chain.go new file mode 100644 index 0000000000..7b36f6a23d --- /dev/null +++ b/pkg/bootstrap/chain.go @@ -0,0 +1,50 @@ +/* +Copyright 2023 The Kubernetes 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 bootstrap + +import ( + "context" + "fmt" + "net/http" + + "k8s.io/klog/v2" +) + +// NewChainVerifier creates a new Verifier that will return the first positive verification from the provided Verifiers. +func NewChainVerifier(chain ...Verifier) Verifier { + return &ChainVerifier{chain: chain} +} + +// ChainVerifier wraps multiple Verifiers; the first positive verification from any Verifier will be returned. +type ChainVerifier struct { + chain []Verifier +} + +// VerifyToken will return the first positive verification from any Verifier in the chain. +func (v *ChainVerifier) VerifyToken(ctx context.Context, rawRequest *http.Request, token string, body []byte) (*VerifyResult, error) { + for _, verifier := range v.chain { + result, err := verifier.VerifyToken(ctx, rawRequest, token, body) + if err == nil { + return result, nil + } + if err == ErrNotThisVerifier { + continue + } + klog.Infof("failed to verify token: %v", err) + } + return nil, fmt.Errorf("unable to verify token") +} diff --git a/upup/pkg/fi/cloudup/awsup/aws_verifier.go b/upup/pkg/fi/cloudup/awsup/aws_verifier.go index 8b98010c00..f00aac8ecf 100644 --- a/upup/pkg/fi/cloudup/awsup/aws_verifier.go +++ b/upup/pkg/fi/cloudup/awsup/aws_verifier.go @@ -123,7 +123,7 @@ type ResponseMetadata struct { func (a awsVerifier) VerifyToken(ctx context.Context, rawRequest *http.Request, token string, body []byte) (*bootstrap.VerifyResult, error) { if !strings.HasPrefix(token, AWSAuthenticationTokenPrefix) { - return nil, fmt.Errorf("incorrect authorization type") + return nil, bootstrap.ErrNotThisVerifier } token = strings.TrimPrefix(token, AWSAuthenticationTokenPrefix) diff --git a/upup/pkg/fi/cloudup/azure/verifier.go b/upup/pkg/fi/cloudup/azure/verifier.go index 5c853bbf90..355e26f965 100644 --- a/upup/pkg/fi/cloudup/azure/verifier.go +++ b/upup/pkg/fi/cloudup/azure/verifier.go @@ -60,7 +60,7 @@ func NewAzureVerifier(ctx context.Context, opt *AzureVerifierOptions) (bootstrap func (a azureVerifier) VerifyToken(ctx context.Context, rawRequest *http.Request, token string, body []byte) (*bootstrap.VerifyResult, error) { if !strings.HasPrefix(token, AzureAuthenticationTokenPrefix) { - return nil, fmt.Errorf("incorrect authorization type") + return nil, bootstrap.ErrNotThisVerifier } v := strings.Split(strings.TrimPrefix(token, AzureAuthenticationTokenPrefix), " ") diff --git a/upup/pkg/fi/cloudup/do/verifier.go b/upup/pkg/fi/cloudup/do/verifier.go index d0596a26a8..d5982366ef 100644 --- a/upup/pkg/fi/cloudup/do/verifier.go +++ b/upup/pkg/fi/cloudup/do/verifier.go @@ -61,7 +61,7 @@ func NewVerifier(ctx context.Context, opt *DigitalOceanVerifierOptions) (bootstr func (o digitalOceanVerifier) VerifyToken(ctx context.Context, rawRequest *http.Request, token string, body []byte) (*bootstrap.VerifyResult, error) { if !strings.HasPrefix(token, DOAuthenticationTokenPrefix) { - return nil, fmt.Errorf("incorrect authorization type") + return nil, bootstrap.ErrNotThisVerifier } serverIDString := strings.TrimPrefix(token, DOAuthenticationTokenPrefix) diff --git a/upup/pkg/fi/cloudup/gce/tpm/gcetpmverifier/tpmverifier.go b/upup/pkg/fi/cloudup/gce/tpm/gcetpmverifier/tpmverifier.go index 71d30a3878..ce7773a322 100644 --- a/upup/pkg/fi/cloudup/gce/tpm/gcetpmverifier/tpmverifier.go +++ b/upup/pkg/fi/cloudup/gce/tpm/gcetpmverifier/tpmverifier.go @@ -71,7 +71,7 @@ func (v *tpmVerifier) VerifyToken(ctx context.Context, rawRequest *http.Request, // Thankfully the GCE SDK does seem to escape the parameters correctly, for example. if !strings.HasPrefix(authToken, gcetpm.GCETPMAuthenticationTokenPrefix) { - return nil, fmt.Errorf("incorrect authorization type") + return nil, bootstrap.ErrNotThisVerifier } authToken = strings.TrimPrefix(authToken, gcetpm.GCETPMAuthenticationTokenPrefix) diff --git a/upup/pkg/fi/cloudup/hetzner/verifier.go b/upup/pkg/fi/cloudup/hetzner/verifier.go index eb7743684c..e939f7ad3b 100644 --- a/upup/pkg/fi/cloudup/hetzner/verifier.go +++ b/upup/pkg/fi/cloudup/hetzner/verifier.go @@ -59,7 +59,7 @@ func NewHetznerVerifier(opt *HetznerVerifierOptions) (bootstrap.Verifier, error) func (h hetznerVerifier) VerifyToken(ctx context.Context, rawRequest *http.Request, token string, body []byte) (*bootstrap.VerifyResult, error) { if !strings.HasPrefix(token, HetznerAuthenticationTokenPrefix) { - return nil, fmt.Errorf("incorrect authorization type") + return nil, bootstrap.ErrNotThisVerifier } token = strings.TrimPrefix(token, HetznerAuthenticationTokenPrefix) diff --git a/upup/pkg/fi/cloudup/openstack/verifier.go b/upup/pkg/fi/cloudup/openstack/verifier.go index 0268e162b0..dc25a6c471 100644 --- a/upup/pkg/fi/cloudup/openstack/verifier.go +++ b/upup/pkg/fi/cloudup/openstack/verifier.go @@ -122,7 +122,7 @@ func readKubeConfig() (*restclient.Config, error) { func (o openstackVerifier) VerifyToken(ctx context.Context, rawRequest *http.Request, token string, body []byte) (*bootstrap.VerifyResult, error) { if !strings.HasPrefix(token, OpenstackAuthenticationTokenPrefix) { - return nil, fmt.Errorf("incorrect authorization type") + return nil, bootstrap.ErrNotThisVerifier } serverID := strings.TrimPrefix(token, OpenstackAuthenticationTokenPrefix) diff --git a/upup/pkg/fi/cloudup/scaleway/verifier.go b/upup/pkg/fi/cloudup/scaleway/verifier.go index d7de18d4d7..0045830bb4 100644 --- a/upup/pkg/fi/cloudup/scaleway/verifier.go +++ b/upup/pkg/fi/cloudup/scaleway/verifier.go @@ -60,7 +60,7 @@ func NewScalewayVerifier(ctx context.Context, opt *ScalewayVerifierOptions) (boo func (v scalewayVerifier) VerifyToken(ctx context.Context, rawRequest *http.Request, token string, body []byte) (*bootstrap.VerifyResult, error) { if !strings.HasPrefix(token, ScalewayAuthenticationTokenPrefix) { - return nil, fmt.Errorf("incorrect authorization type") + return nil, bootstrap.ErrNotThisVerifier } serverID := strings.TrimPrefix(token, ScalewayAuthenticationTokenPrefix)