Skip to content

Commit

Permalink
initial changes
Browse files Browse the repository at this point in the history
  • Loading branch information
pawanpinjarkar committed May 7, 2024
1 parent 84315b0 commit 8eebc2d
Show file tree
Hide file tree
Showing 16 changed files with 183 additions and 10 deletions.
7 changes: 7 additions & 0 deletions cmd/agentbasedinstaller/client/main.go
Expand Up @@ -35,13 +35,15 @@ import (

"github.com/kelseyhightower/envconfig"
"github.com/openshift/assisted-service/client"
"github.com/openshift/assisted-service/pkg/auth"
log "github.com/sirupsen/logrus"
)

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 {
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down
12 changes: 12 additions & 0 deletions cmd/main.go
Expand Up @@ -2,6 +2,7 @@ package main

import (
"context"
"encoding/base64"
"encoding/json"
"flag"
"fmt"
Expand Down Expand Up @@ -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)
Expand Down
3 changes: 1 addition & 2 deletions internal/bminventory/inventory_v2_handlers.go
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions internal/cluster/auth.go
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions internal/controller/controllers/agent_controller.go
Expand Up @@ -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
}
Expand Down
1 change: 1 addition & 0 deletions internal/controller/controllers/agent_reclaimer.go
Expand Up @@ -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)
Expand Down
Expand Up @@ -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
}
Expand Down
1 change: 1 addition & 0 deletions internal/controller/controllers/common.go
Expand Up @@ -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
}
Expand Down
1 change: 1 addition & 0 deletions internal/controller/controllers/infraenv_controller.go
Expand Up @@ -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
}
Expand Down
6 changes: 6 additions & 0 deletions internal/gencrypto/token.go
@@ -1,6 +1,7 @@
package gencrypto

import (
"encoding/base64"
"net/url"
"os"
"time"
Expand All @@ -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")
}
Expand Down
1 change: 1 addition & 0 deletions internal/host/hostcommands/download_boot_artifacts_cmd.go
Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions internal/ignition/ignition.go
Expand Up @@ -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 {
Expand Down
119 changes: 119 additions & 0 deletions 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
}
20 changes: 12 additions & 8 deletions pkg/auth/authenticator.go
Expand Up @@ -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 {
Expand All @@ -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:""`
Expand All @@ -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)
}
Expand Down
14 changes: 14 additions & 0 deletions pkg/auth/authenticator_test.go
Expand Up @@ -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())

})
})
3 changes: 3 additions & 0 deletions pkg/auth/local_authenticator.go
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 })

Expand Down

0 comments on commit 8eebc2d

Please sign in to comment.