From 8eebc2dea8537d0ec02aaaea031b767a7f0faf80 Mon Sep 17 00:00:00 2001 From: Pawan Pinjarkar Date: Tue, 19 Mar 2024 11:26:37 -0400 Subject: [PATCH] initial changes --- cmd/agentbasedinstaller/client/main.go | 7 ++ cmd/main.go | 12 ++ internal/bminventory/inventory_v2_handlers.go | 3 +- internal/cluster/auth.go | 2 + .../controllers/agent_controller.go | 1 + .../controller/controllers/agent_reclaimer.go | 1 + .../clusterdeployments_controller.go | 1 + internal/controller/controllers/common.go | 1 + .../controllers/infraenv_controller.go | 1 + internal/gencrypto/token.go | 6 + .../download_boot_artifacts_cmd.go | 1 + internal/ignition/ignition.go | 1 + pkg/auth/agent_local_authenticator.go | 119 ++++++++++++++++++ pkg/auth/authenticator.go | 20 +-- pkg/auth/authenticator_test.go | 14 +++ pkg/auth/local_authenticator.go | 3 + 16 files changed, 183 insertions(+), 10 deletions(-) create mode 100644 pkg/auth/agent_local_authenticator.go diff --git a/cmd/agentbasedinstaller/client/main.go b/cmd/agentbasedinstaller/client/main.go index 21c182d579f..87a0f070d47 100644 --- a/cmd/agentbasedinstaller/client/main.go +++ b/cmd/agentbasedinstaller/client/main.go @@ -35,6 +35,7 @@ import ( "github.com/kelseyhightower/envconfig" "github.com/openshift/assisted-service/client" + "github.com/openshift/assisted-service/pkg/auth" log "github.com/sirupsen/logrus" ) @@ -42,6 +43,7 @@ const failureOutputPath = "/var/run/agent-installer/host-config-failures" var Options struct { ServiceBaseUrl string `envconfig:"SERVICE_BASE_URL" default:""` + Token string `envconfig:"PULL_SECRET_TOKEN" default:""` } var RegisterOptions struct { @@ -82,6 +84,10 @@ func main() { } u.Path = path.Join(u.Path, client.DefaultBasePath) clientConfig.URL = u + + userToken := Options.Token + clientConfig.AuthInfo = auth.UserAuthHeaderWriter("bearer " + userToken) + bmInventory := client.New(clientConfig) ctx := context.Background() log.Info("SERVICE_BASE_URL: " + Options.ServiceBaseUrl) @@ -96,6 +102,7 @@ func main() { if len(os.Args) < 2 { log.Fatal("No subcommand specified") } + switch os.Args[1] { case "register": // registers both cluster and infraenv diff --git a/cmd/main.go b/cmd/main.go index 4973a34d6ef..a101a2a781a 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -2,6 +2,7 @@ package main import ( "context" + "encoding/base64" "encoding/json" "flag" "fmt" @@ -269,6 +270,17 @@ func main() { usageManager := usage.NewManager(log, notificationStream) ocmClient := getOCMClient(log) + // decode only for agent installer + // if Options.Auth.AuthType == auth.TypeAgentLocal { + decodedECPublicKeyPEM, err := base64.StdEncoding.DecodeString(Options.Auth.ECPublicKeyPEM) + Options.Auth.ECPublicKeyPEM = string(decodedECPublicKeyPEM) + failOnError(err, "Error decoding public key:") + + decodedECPrivateKeyPEM, err := base64.StdEncoding.DecodeString(Options.Auth.ECPrivateKeyPEM) + Options.Auth.ECPrivateKeyPEM = string(decodedECPrivateKeyPEM) + failOnError(err, "Error decoding private key:") + // } + authHandler, err := auth.NewAuthenticator(&Options.Auth, ocmClient, log.WithField("pkg", "auth"), db) failOnError(err, "failed to create authenticator") authzHandler := auth.NewAuthzHandler(&Options.Auth, ocmClient, log.WithField("pkg", "authz"), db) diff --git a/internal/bminventory/inventory_v2_handlers.go b/internal/bminventory/inventory_v2_handlers.go index 65631785328..3c7e0dbc98a 100644 --- a/internal/bminventory/inventory_v2_handlers.go +++ b/internal/bminventory/inventory_v2_handlers.go @@ -756,7 +756,7 @@ func (b *bareMetalInventory) GetInfraEnvDownloadURL(ctx context.Context, params func (b *bareMetalInventory) generateShortImageDownloadURL(infraEnvID, imageType, version, arch, imageTokenKey string) (string, *strfmt.DateTime, error) { switch b.authHandler.AuthType() { - case auth.TypeLocal: + case auth.TypeLocal, auth.TypeAgentLocal: return b.generateShortImageDownloadURLByAPIKey(infraEnvID, imageType, version, arch) case auth.TypeRHSSO: return b.generateShortImageDownloadURLByToken(infraEnvID, imageType, version, arch, imageTokenKey) @@ -769,7 +769,6 @@ func (b *bareMetalInventory) generateShortImageDownloadURL(infraEnvID, imageType func (b *bareMetalInventory) generateShortImageDownloadURLByAPIKey(infraEnvID, imageType, version, arch string) (string, *strfmt.DateTime, error) { var expiresAt strfmt.DateTime - token, err := gencrypto.LocalJWT(infraEnvID, gencrypto.InfraEnvKey) if err != nil { return "", nil, errors.Wrapf(err, "failed to generate token for infraEnv %s", infraEnvID) diff --git a/internal/cluster/auth.go b/internal/cluster/auth.go index d354c61f576..cd673cf6048 100644 --- a/internal/cluster/auth.go +++ b/internal/cluster/auth.go @@ -32,6 +32,8 @@ func AgentToken(resource interface{}, authType auth.AuthType) (token string, err token, err = cloudPullSecretToken(pullSecret) case auth.TypeLocal: token, err = gencrypto.LocalJWT(resId, gencrypto.InfraEnvKey) + case auth.TypeAgentLocal: + token, err = gencrypto.LocalJWT(resId, gencrypto.InfraEnvKey) case auth.TypeNone: token = "" default: diff --git a/internal/controller/controllers/agent_controller.go b/internal/controller/controllers/agent_controller.go index 71a7a97bf06..bb4046784c4 100644 --- a/internal/controller/controllers/agent_controller.go +++ b/internal/controller/controllers/agent_controller.go @@ -928,6 +928,7 @@ func generateControllerLogsDownloadURL(baseURL string, clusterID string, authTyp } downloadURL := fmt.Sprintf("%s%s", baseURL, u.RequestURI()) + // might need to also check fot agent-installer-local if authType != auth.TypeLocal { return downloadURL, nil } diff --git a/internal/controller/controllers/agent_reclaimer.go b/internal/controller/controllers/agent_reclaimer.go index e42cf94622c..4ed3cd6fd91 100644 --- a/internal/controller/controllers/agent_reclaimer.go +++ b/internal/controller/controllers/agent_reclaimer.go @@ -141,6 +141,7 @@ func spokeReclaimSecretName(infraEnvID string) string { func (r *agentReclaimer) ensureSpokeAgentSecret(ctx context.Context, c client.Client, log logrus.FieldLogger, infraEnvID string) error { authToken := "" + // might need to add agent-install-local if r.AuthType == auth.TypeLocal { var err error authToken, err = gencrypto.LocalJWT(infraEnvID, gencrypto.InfraEnvKey) diff --git a/internal/controller/controllers/clusterdeployments_controller.go b/internal/controller/controllers/clusterdeployments_controller.go index 6cfc63d977e..4a5a8dd0a19 100644 --- a/internal/controller/controllers/clusterdeployments_controller.go +++ b/internal/controller/controllers/clusterdeployments_controller.go @@ -2264,6 +2264,7 @@ func (r *ClusterDeploymentsReconciler) generateControllerLogsDownloadURL(cluster downloadURL := fmt.Sprintf("%s%s/v2/clusters/%s/logs", r.ServiceBaseURL, restclient.DefaultBasePath, cluster.ID.String()) + // might need to add agent-install-local if r.AuthType != auth.TypeLocal { return downloadURL, nil } diff --git a/internal/controller/controllers/common.go b/internal/controller/controllers/common.go index 915c0be8741..a3839ea79af 100644 --- a/internal/controller/controllers/common.go +++ b/internal/controller/controllers/common.go @@ -368,6 +368,7 @@ func IngressVipsEntriesToArray(entries []string) []*models.IngressVip { } func signURL(urlString string, authType auth.AuthType, id string, keyType gencrypto.LocalJWTKeyType) (string, error) { + // might need to add agent-install-local if authType != auth.TypeLocal { return urlString, nil } diff --git a/internal/controller/controllers/infraenv_controller.go b/internal/controller/controllers/infraenv_controller.go index 9e805e96f60..58469548678 100644 --- a/internal/controller/controllers/infraenv_controller.go +++ b/internal/controller/controllers/infraenv_controller.go @@ -670,6 +670,7 @@ func generateStaticNetworkConfigDownloadURL(baseURL string, infraEnvId string, a } downloadURL := fmt.Sprintf("%s%s", baseURL, u.RequestURI()) + // might need to add agent-install-local if authType != auth.TypeLocal { return downloadURL, nil } diff --git a/internal/gencrypto/token.go b/internal/gencrypto/token.go index 63195b23e81..5c5949a043c 100644 --- a/internal/gencrypto/token.go +++ b/internal/gencrypto/token.go @@ -1,6 +1,7 @@ package gencrypto import ( + "encoding/base64" "net/url" "os" "time" @@ -24,6 +25,11 @@ type CryptoPair struct { func LocalJWT(id string, keyType LocalJWTKeyType) (string, error) { key, ok := os.LookupEnv("EC_PRIVATE_KEY_PEM") + // if authType == auth.TypeAgentLocal { + // decode only for agent installer + decodedECPrivateKeyPEM, _ := base64.StdEncoding.DecodeString(key) + key = string(decodedECPrivateKeyPEM) + // } if !ok || key == "" { return "", errors.Errorf("EC_PRIVATE_KEY_PEM not found") } diff --git a/internal/host/hostcommands/download_boot_artifacts_cmd.go b/internal/host/hostcommands/download_boot_artifacts_cmd.go index 67376441ff6..962e987f1f5 100644 --- a/internal/host/hostcommands/download_boot_artifacts_cmd.go +++ b/internal/host/hostcommands/download_boot_artifacts_cmd.go @@ -59,6 +59,7 @@ func (c *downloadBootArtifactsCmd) GetSteps(ctx context.Context, host *models.Ho return nil, fmt.Errorf("failed to generate urls for DownloadBootArtifactsRequest: %w", err) } // Reclaiming a host is only used in the operator scenario (not SaaS) so other auth types don't need to be considered + // might need to add agent-install-local if c.authType == auth.TypeLocal { bootArtifactURLs.InitrdURL, err = gencrypto.SignURL(bootArtifactURLs.InitrdURL, infraEnv.ID.String(), gencrypto.InfraEnvKey) if err != nil { diff --git a/internal/ignition/ignition.go b/internal/ignition/ignition.go index 892bbfa4635..e60eee6412f 100644 --- a/internal/ignition/ignition.go +++ b/internal/ignition/ignition.go @@ -1768,6 +1768,7 @@ func (ib *ignitionBuilder) shouldAppendOKDFiles(ctx context.Context, infraEnv *c return okdRpmsImage, true } +// here assisted service calls pullSecretToken which for agent installer or otherwise as well is called as an auth token func (ib *ignitionBuilder) FormatDiscoveryIgnitionFile(ctx context.Context, infraEnv *common.InfraEnv, cfg IgnitionConfig, safeForLogs bool, authType auth.AuthType, overrideDiscoveryISOType string) (string, error) { pullSecretToken, err := clusterPkg.AgentToken(infraEnv, authType) if err != nil { diff --git a/pkg/auth/agent_local_authenticator.go b/pkg/auth/agent_local_authenticator.go new file mode 100644 index 00000000000..dd7ec40fd22 --- /dev/null +++ b/pkg/auth/agent_local_authenticator.go @@ -0,0 +1,119 @@ +package auth + +import ( + "crypto" + "net/http" + "strings" + "time" + + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/security" + "github.com/golang-jwt/jwt/v4" + "github.com/openshift/assisted-service/internal/common" + "github.com/openshift/assisted-service/internal/gencrypto" + + "github.com/openshift/assisted-service/pkg/ocm" + "github.com/patrickmn/go-cache" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "gorm.io/gorm" +) + +type AgentLocalAuthenticator struct { + cache *cache.Cache + db *gorm.DB + log logrus.FieldLogger + publicKey crypto.PublicKey +} + +func NewAgentLocalAuthenticator(cfg *Config, log logrus.FieldLogger, db *gorm.DB) (*AgentLocalAuthenticator, error) { + if cfg.ECPublicKeyPEM == "" { + return nil, errors.Errorf("agent installer local authentication requires an ecdsa Public Key") + } + logrus.Infof("*** in NewAgentLocalAuthenticator, public key is %s", cfg.ECPublicKeyPEM) + key, err := jwt.ParseECPublicKeyFromPEM([]byte(cfg.ECPublicKeyPEM)) + if err != nil { + return nil, err + } + + a := &AgentLocalAuthenticator{ + cache: cache.New(10*time.Minute, 30*time.Minute), + db: db, + log: log, + publicKey: key, + } + + return a, nil +} + +var _ Authenticator = &AgentLocalAuthenticator{} + +func (a *AgentLocalAuthenticator) AuthType() AuthType { + return TypeAgentLocal +} + +func (a *AgentLocalAuthenticator) EnableOrgTenancy() bool { + return false +} + +func (a *AgentLocalAuthenticator) EnableOrgBasedFeatureGates() bool { + return false +} + +func (a *AgentLocalAuthenticator) AuthAgentAuth(token string) (interface{}, error) { + // Trim the "bearer" prefix if present. Check without removing + token = strings.TrimPrefix(token, "bearer ") + token = strings.TrimPrefix(token, "Bearer ") + + t, err := validateToken(token, a.publicKey) + if err != nil { + a.log.WithError(err).Error("failed to validate token") + return nil, common.NewInfraError(http.StatusUnauthorized, err) + } + claims, ok := t.Claims.(jwt.MapClaims) + if !ok { + err := errors.Errorf("failed to parse JWT token claims") + a.log.Error(err) + return nil, common.NewInfraError(http.StatusUnauthorized, err) + } + + infraEnvID, infraEnvOk := claims[string(gencrypto.InfraEnvKey)].(string) + if !infraEnvOk { + err := errors.Errorf("claims are incorrectly formatted") + a.log.Error(err) + return nil, common.NewInfraError(http.StatusUnauthorized, err) + } + if infraEnvOk { + _, exists := a.cache.Get(infraEnvID) + if !exists { + if infraEnvExists(a.db, infraEnvID) { + a.cache.Set(infraEnvID, "", cache.DefaultExpiration) + } + // not useful for agent. Always errors because infraenv needs to be registered? + // else { + // logrus.Infof("infraEnv %s does not exist", infraEnvID) + // err := errors.Errorf("infraEnv %s does not exist", infraEnvID) + // return nil, common.NewInfraError(http.StatusUnauthorized, err) + // } + } + a.log.Infof("Authenticating infraEnv %s JWT", infraEnvID) + } + + return ocm.AdminPayload(), nil +} + +func (a *AgentLocalAuthenticator) AuthUserAuth(token string) (interface{}, error) { + return a.AuthAgentAuth(token) +} + +func (a *AgentLocalAuthenticator) AuthURLAuth(token string) (interface{}, error) { + return a.AuthAgentAuth(token) +} + +func (a *AgentLocalAuthenticator) AuthImageAuth(_ string) (interface{}, error) { + return nil, common.NewInfraError(http.StatusUnauthorized, errors.Errorf("Image Authentication not allowed for agent local auth")) +} + +func (a *AgentLocalAuthenticator) CreateAuthenticator() func(_, _ string, _ security.TokenAuthentication) runtime.Authenticator { + return security.APIKeyAuth +} diff --git a/pkg/auth/authenticator.go b/pkg/auth/authenticator.go index dc41717b32f..0ca085f1ff7 100644 --- a/pkg/auth/authenticator.go +++ b/pkg/auth/authenticator.go @@ -13,10 +13,11 @@ import ( type AuthType string const ( - TypeEmpty AuthType = "" - TypeNone AuthType = "none" - TypeRHSSO AuthType = "rhsso" - TypeLocal AuthType = "local" + TypeEmpty AuthType = "" + TypeNone AuthType = "none" + TypeRHSSO AuthType = "rhsso" + TypeLocal AuthType = "local" + TypeAgentLocal AuthType = "agent-installer-local" ) type Authenticator interface { @@ -31,10 +32,11 @@ type Authenticator interface { } type Config struct { - AuthType AuthType `envconfig:"AUTH_TYPE" default:""` - JwkCert string `envconfig:"JWKS_CERT"` - JwkCertURL string `envconfig:"JWKS_URL" default:"https://api.openshift.com/.well-known/jwks.json"` - ECPublicKeyPEM string `envconfig:"EC_PUBLIC_KEY_PEM"` + AuthType AuthType `envconfig:"AUTH_TYPE" default:""` + JwkCert string `envconfig:"JWKS_CERT"` + JwkCertURL string `envconfig:"JWKS_URL" default:"https://api.openshift.com/.well-known/jwks.json"` + ECPublicKeyPEM string `envconfig:"EC_PUBLIC_KEY_PEM"` + ECPrivateKeyPEM string `envconfig:"EC_PRIVATE_KEY_PEM"` // Will be split with "," as separator AllowedDomains string `envconfig:"ALLOWED_DOMAINS" default:""` AdminUsers []string `envconfig:"ADMIN_USERS" default:""` @@ -50,6 +52,8 @@ func NewAuthenticator(cfg *Config, ocmClient *ocm.Client, log logrus.FieldLogger a = NewNoneAuthenticator(log) case TypeLocal: a, err = NewLocalAuthenticator(cfg, log, db) + case TypeAgentLocal: + a, err = NewAgentLocalAuthenticator(cfg, log, db) default: err = fmt.Errorf("invalid authenticator type %v", cfg.AuthType) } diff --git a/pkg/auth/authenticator_test.go b/pkg/auth/authenticator_test.go index 9ed48f3d8e3..97e4e249382 100644 --- a/pkg/auth/authenticator_test.go +++ b/pkg/auth/authenticator_test.go @@ -48,5 +48,19 @@ var _ = Describe("NewAuthenticator", func() { Expect(err).ToNot(HaveOccurred()) _, ok = a.(*LocalAuthenticator) Expect(ok).To(BeTrue()) + + // AgentLocalAuthenticator + pubKey, _, err = gencrypto.ECDSAKeyPairPEM() + Expect(err).ToNot(HaveOccurred()) + config = &Config{ + AuthType: TypeAgentLocal, + ECPublicKeyPEM: pubKey, + } + + a, err = NewAuthenticator(config, nil, logrus.New(), nil) + Expect(err).ToNot(HaveOccurred()) + _, ok = a.(*LocalAuthenticator) + Expect(ok).To(BeTrue()) + }) }) diff --git a/pkg/auth/local_authenticator.go b/pkg/auth/local_authenticator.go index d13413a22e5..5a246c21ff3 100644 --- a/pkg/auth/local_authenticator.go +++ b/pkg/auth/local_authenticator.go @@ -29,6 +29,7 @@ func NewLocalAuthenticator(cfg *Config, log logrus.FieldLogger, db *gorm.DB) (*L if cfg.ECPublicKeyPEM == "" { return nil, errors.Errorf("local authentication requires an ecdsa Public Key") } + logrus.Infof("*** in local_authenticator cfg.ECPublicKeyPEM =%s", cfg.ECPublicKeyPEM) key, err := jwt.ParseECPublicKeyFromPEM([]byte(cfg.ECPublicKeyPEM)) if err != nil { @@ -123,6 +124,8 @@ func (a *LocalAuthenticator) CreateAuthenticator() func(_, _ string, _ security. } func validateToken(token string, pub crypto.PublicKey) (*jwt.Token, error) { + logrus.Infof("**** validateToken token=%s", token) + logrus.Infof("**** pub=%s", pub) parser := &jwt.Parser{ValidMethods: []string{jwt.SigningMethodES256.Alg()}} parsed, err := parser.Parse(token, func(t *jwt.Token) (interface{}, error) { return pub, nil })