feat: Support multiple token verifiers in kops-controller

This will allow us to support nodes running in multiple clouds.  If we
don't configure multiple verifiers, this should be a no-op.
This commit is contained in:
justinsb 2023-11-29 22:44:45 -05:00
parent 6f7fcb0a9a
commit 592b575412
10 changed files with 97 additions and 23 deletions

View File

@ -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")

View File

@ -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")

50
pkg/bootstrap/chain.go Normal file
View File

@ -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")
}

View File

@ -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)

View File

@ -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), " ")

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)