From afb839285c80a8ffbba0abc88f8d15c8e9c778ce 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 | 125 ++++++++++++++++++++++++- pkg/auth/agent_local_authenticator.go | 112 ++++++++++++++++++++++ pkg/auth/authenticator.go | 11 ++- pkg/auth/authenticator_test.go | 14 +++ 4 files changed, 257 insertions(+), 5 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 6800a94bb5b..cf0e1cde63b 100644 --- a/cmd/agentbasedinstaller/client/main.go +++ b/cmd/agentbasedinstaller/client/main.go @@ -23,25 +23,51 @@ package main import ( "context" + "flag" "fmt" "net/url" "os" + "os/signal" "path" "strings" + "syscall" "time" + "github.com/NYTimes/gziphandler" "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/openshift/assisted-image-service/pkg/servers" "github.com/openshift/assisted-service/cmd/agentbasedinstaller" + "github.com/openshift/assisted-service/internal/common" + "github.com/openshift/assisted-service/internal/spec" + "github.com/openshift/assisted-service/pkg/app" + "github.com/openshift/assisted-service/pkg/auth" + dbPkg "github.com/openshift/assisted-service/pkg/db" + "github.com/openshift/assisted-service/pkg/requestid" + "github.com/openshift/assisted-service/restapi" + "gorm.io/driver/postgres" + "gorm.io/gorm" + "gorm.io/gorm/logger" + "k8s.io/apimachinery/pkg/util/wait" "github.com/kelseyhightower/envconfig" "github.com/openshift/assisted-service/client" + "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus" ) const failureOutputPath = "/var/run/agent-installer/host-config-failures" var Options struct { - ServiceBaseUrl string `envconfig:"SERVICE_BASE_URL" default:""` + Auth auth.Config + DBConfig dbPkg.Config + ServiceBaseUrl string `envconfig:"SERVICE_BASE_URL" default:""` + MaxIdleConns int `envconfig:"DB_MAX_IDLE_CONNECTIONS" default:"50"` + MaxOpenConns int `envconfig:"DB_MAX_OPEN_CONNECTIONS" default:"90"` + ConnMaxLifetime time.Duration `envconfig:"DB_CONNECTIONS_MAX_LIFETIME" default:"30m"` + HTTPSKeyFile string `envconfig:"HTTPS_KEY_FILE" default:""` + HTTPSCertFile string `envconfig:"HTTPS_CERT_FILE" default:""` + HTTPListenPort string `envconfig:"HTTP_LISTEN_PORT" default:""` } var RegisterOptions struct { @@ -73,6 +99,7 @@ func main() { if err != nil { log.Fatal(err.Error()) } + clientConfig := client.Config{} u, parseErr := url.Parse(Options.ServiceBaseUrl) @@ -95,6 +122,65 @@ func main() { if len(os.Args) < 2 { log.Fatal("No subcommand specified") } + + // Connect to db + db := setupDB(log) + defer common.CloseDB(db) + + authHandler, err := auth.NewAuthenticator(&Options.Auth, nil, log.WithField("pkg", "auth"), db) + failOnError := func(err error, msg string, args ...interface{}) { + if err != nil { + log.WithError(err).Fatalf(msg, args...) + } + } + failOnError(err, "failed to create authenticator") + + h, err := restapi.Handler(restapi.Config{ + AuthAgentAuth: authHandler.AuthAgentAuth, + AuthUserAuth: authHandler.AuthUserAuth, + AuthURLAuth: authHandler.AuthURLAuth, + AuthImageAuth: authHandler.AuthImageAuth, + AuthImageURLAuth: authHandler.AuthImageAuth, + // APIKeyAuthenticator: authHandler.CreateAuthenticator(), + // Authorizer: authzHandler.CreateAuthorizer(), + // InstallerAPI: bm, + // EventsAPI: events, + Logger: log.Printf, + // VersionsAPI: versionsAPIHandler, + // ManagedDomainsAPI: domainHandler, + // InnerMiddleware: innerHandler(), + // ManifestsAPI: manifestsApi, + // OperatorsAPI: operatorsHandler, + // JSONConsumer: jsonConsumer, + }) + failOnError(err, "Failed to init rest handler") + + h = gziphandler.GzipHandler(h) + h = app.WithMetricsResponderMiddleware(h) + // h = app.WithHealthMiddleware(h, []*thread.Thread{hostStateMonitor, clusterStateMonitor}, + // log.WithField("pkg", "healthcheck"), Options.LivenessValidationTimeout) + h = requestid.Middleware(h) + h = spec.WithSpecMiddleware(h) + + port := flag.String("port", "8090", "define port that the service will listen to") + flag.Parse() + + serverInfo := servers.New(Options.HTTPListenPort, swag.StringValue(port), Options.HTTPSKeyFile, Options.HTTPSCertFile) + + // Interrupt servers on SIGINT/SIGTERM + stop := make(chan os.Signal, 1) + signal.Notify(stop, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) + + if serverInfo.HTTP != nil { + serverInfo.HTTP.Handler = h + } + if serverInfo.HTTPS != nil { + serverInfo.HTTPS.Handler = h + } + serverInfo.ListenAndServe() + <-stop + serverInfo.Shutdown() + switch os.Args[1] { case "register": // registers both cluster and infraenv @@ -145,6 +231,43 @@ func register(ctx context.Context, log *log.Logger, bmInventory *client.Assisted return modelsInfraEnv.ID.String() } +func setupDB(log logrus.FieldLogger) *gorm.DB { + dbConnectionStr := fmt.Sprintf("host=%s port=%s user=%s database=%s password=%s sslmode=disable", + Options.DBConfig.Host, Options.DBConfig.Port, Options.DBConfig.User, Options.DBConfig.Name, Options.DBConfig.Pass) + var db *gorm.DB + var err error + // Tries to open a db connection every 2 seconds + retryInterval := 2 * time.Second + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + log.Info("Connecting to DB") + wait.UntilWithContext(ctx, func(ctx context.Context) { + db, err = gorm.Open(postgres.Open(dbConnectionStr), &gorm.Config{ + DisableForeignKeyConstraintWhenMigrating: true, + Logger: logger.Default.LogMode(logger.Silent), + }) + if err != nil { + log.WithError(err).Info("Failed to connect to DB, retrying") + return + } + sqlDB, err := db.DB() + if err != nil { + log.WithError(err).Info("Failed to get sqlDB, retrying") + common.CloseDB(db) + return + } + sqlDB.SetMaxIdleConns(Options.MaxIdleConns) + sqlDB.SetMaxOpenConns(Options.MaxOpenConns) + sqlDB.SetConnMaxLifetime(Options.ConnMaxLifetime) + cancel() + }, retryInterval) + if ctx.Err().Error() == context.DeadlineExceeded.Error() { + log.WithError(ctx.Err()).Fatal("Timed out connecting to DB") + } + log.Info("Connected to DB") + return db +} + func registerCluster(ctx context.Context, log *log.Logger, bmInventory *client.AssistedInstall) string { err := envconfig.Process("", &RegisterOptions) 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..197147168b1 --- /dev/null +++ b/pkg/auth/agent_local_authenticator.go @@ -0,0 +1,112 @@ +package auth + +import ( + "crypto" + "net/http" + "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") + } + + 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) { + 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) + } else { + err := errors.Errorf("infraEnv %s does not exist", infraEnvID) + return nil, common.NewInfraError(http.StatusUnauthorized, err) + } + } + a.log.Debugf("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..0052642890e 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 { @@ -50,6 +51,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()) + }) })