Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AGENT-869: Implement a new auth type for ABI #6174

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 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:"AGENT_AUTH_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(userToken)

bmInventory := client.New(clientConfig)
ctx := context.Background()
log.Info("SERVICE_BASE_URL: " + Options.ServiceBaseUrl)
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:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you ever using the download URL we generate?

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: 1 addition & 1 deletion internal/cluster/auth.go
Expand Up @@ -30,7 +30,7 @@ func AgentToken(resource interface{}, authType auth.AuthType) (token string, err
switch authType {
case auth.TypeRHSSO:
token, err = cloudPullSecretToken(pullSecret)
case auth.TypeLocal:
case auth.TypeLocal, auth.TypeAgentLocal:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also only ever used when building the discovery ISO. Are you guys using that?

token, err = gencrypto.LocalJWT(resId, gencrypto.InfraEnvKey)
case auth.TypeNone:
token = ""
Expand Down
114 changes: 114 additions & 0 deletions pkg/auth/agent_local_authenticator.go
@@ -0,0 +1,114 @@
package auth

import (
"crypto"
"encoding/base64"
"net/http"
"os"

"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/pkg/errors"
"github.com/sirupsen/logrus"
)

type AgentLocalAuthenticator struct {
log logrus.FieldLogger
publicKey crypto.PublicKey
}

func NewAgentLocalAuthenticator(cfg *Config, log logrus.FieldLogger) (*AgentLocalAuthenticator, error) {
if cfg.ECPublicKeyPEM == "" {
return nil, errors.Errorf("agent installer local authentication requires an ecdsa Public Key")
}

// When generating an agent ISO, the Agent installer creates ECDSA public/private keys.
// However, the systemd services of the Agent installer fail to parse multiline keys accurately.
// To address this, the keys are encoded in base64 format to condense them into a single line
// before being transmitted to the assisted service.
// Upon reception, the assisted service decodes these keys back to their original multiline format
// for subsequent processing.

decodedECPublicKeyPEM, err := base64.StdEncoding.DecodeString(cfg.ECPublicKeyPEM)
if err != nil {
log.WithError(err).Fatal("Error decoding public key:")
}
cfg.ECPublicKeyPEM = string(decodedECPublicKeyPEM)

decodedECPrivateKeyPEM, err := base64.StdEncoding.DecodeString(cfg.ECPrivateKeyPEM)
if err != nil {
log.WithError(err).Fatal("Error decoding private key:")
}
cfg.ECPrivateKeyPEM = string(decodedECPrivateKeyPEM)
os.Setenv("EC_PRIVATE_KEY_PEM", string(decodedECPrivateKeyPEM))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really don't think this is a good idea.

It looks like you may not actually need the private key at all since you're creating all of the tokens you need from outside the application.

Maybe just don't bother with the private key?


key, err := jwt.ParseECPublicKeyFromPEM([]byte(cfg.ECPublicKeyPEM))
if err != nil {
return nil, err
}

a := &AgentLocalAuthenticator{
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)
}
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
}
112 changes: 112 additions & 0 deletions pkg/auth/agent_local_authenticator_test.go
@@ -0,0 +1,112 @@
package auth

import (
"encoding/base64"
"encoding/json"
"strings"

"github.com/go-openapi/strfmt"
"github.com/google/uuid"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/openshift/assisted-service/internal/common"
"github.com/openshift/assisted-service/internal/gencrypto"
"github.com/sirupsen/logrus"
)

var _ = Describe("AuthAgentAuth", func() {
var (
a *AgentLocalAuthenticator
token string
)

BeforeEach(func() {
infraEnvID := strfmt.UUID(uuid.New().String())

pubKey, privKey, err := gencrypto.ECDSAKeyPairPEM()
Expect(err).ToNot(HaveOccurred())

// Encode to Base64 (Standard encoding)
encodedPrivateKey := base64.StdEncoding.EncodeToString([]byte(privKey))
encodedPubKey := base64.StdEncoding.EncodeToString([]byte(pubKey))

cfg := &Config{
ECPublicKeyPEM: encodedPubKey,
ECPrivateKeyPEM: encodedPrivateKey,
}

token, err = gencrypto.LocalJWTForKey(infraEnvID.String(), privKey, gencrypto.InfraEnvKey)
Expect(err).ToNot(HaveOccurred())

a, err = NewAgentLocalAuthenticator(cfg, logrus.New())
Expect(err).ToNot(HaveOccurred())
})

fakeTokenAlg := func(t string) string {
parts := strings.Split(t, ".")

headerJSON, err := base64.RawStdEncoding.DecodeString(parts[0])
Expect(err).ToNot(HaveOccurred())

header := &map[string]interface{}{}
err = json.Unmarshal(headerJSON, header)
Expect(err).ToNot(HaveOccurred())

// change the algorithm in an otherwise valid token
(*header)["alg"] = "RS256"

headerBytes, err := json.Marshal(header)
Expect(err).ToNot(HaveOccurred())
newHeaderString := base64.RawStdEncoding.EncodeToString(headerBytes)

parts[0] = newHeaderString
return strings.Join(parts, ".")
}

validateErrorResponse := func(err error) {
infraError, ok := err.(*common.InfraErrorResponse)
Expect(ok).To(BeTrue())
Expect(infraError.StatusCode()).To(Equal(int32(401)))
}

It("Validates a token correctly", func() {
_, err := a.AuthAgentAuth(token)
Expect(err).ToNot(HaveOccurred())
})

It("Fails an invalid token", func() {
_, err := a.AuthAgentAuth(token + "asdf")
Expect(err).To(HaveOccurred())
validateErrorResponse(err)
})

It("Works with user auth", func() {
_, err := a.AuthUserAuth(token)
Expect(err).ToNot(HaveOccurred())
})

It("Works with URL auth", func() {
_, err := a.AuthURLAuth(token)
Expect(err).ToNot(HaveOccurred())
})

It("Fails with image auth", func() {
_, err := a.AuthImageAuth(token)
Expect(err).To(HaveOccurred())
})

It("Fails a token with invalid signing method", func() {
newTok := fakeTokenAlg(token)
_, err := a.AuthAgentAuth(newTok)
Expect(err).To(HaveOccurred())
validateErrorResponse(err)
})

It("Fails with an RSA token", func() {
rsaToken, _ := GetTokenAndCert(false)
_, err := a.AuthAgentAuth(rsaToken)
Expect(err).To(HaveOccurred())
validateErrorResponse(err)
})

})
12 changes: 8 additions & 4 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 @@ -40,6 +41,7 @@ type Config struct {
AdminUsers []string `envconfig:"ADMIN_USERS" default:""`
EnableOrgTenancy bool `envconfig:"ENABLE_ORG_TENANCY" default:"false"`
EnableOrgBasedFeatureGates bool `envconfig:"ENABLE_ORG_BASED_FEATURE_GATES" default:"false"`
ECPrivateKeyPEM string `envconfig:"EC_PRIVATE_KEY_PEM"`
}

func NewAuthenticator(cfg *Config, ocmClient *ocm.Client, log logrus.FieldLogger, db *gorm.DB) (a Authenticator, err error) {
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)
default:
err = fmt.Errorf("invalid authenticator type %v", cfg.AuthType)
}
Expand Down
19 changes: 19 additions & 0 deletions pkg/auth/authenticator_test.go
@@ -1,6 +1,8 @@
package auth

import (
"encoding/base64"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/openshift/assisted-service/internal/gencrypto"
Expand Down Expand Up @@ -48,5 +50,22 @@ var _ = Describe("NewAuthenticator", func() {
Expect(err).ToNot(HaveOccurred())
_, ok = a.(*LocalAuthenticator)
Expect(ok).To(BeTrue())

// AgentLocalAuthenticator
pubKey, privKey, err := gencrypto.ECDSAKeyPairPEM()
Expect(err).ToNot(HaveOccurred())
encodedPrivateKey := base64.StdEncoding.EncodeToString([]byte(privKey))
encodedPubKey := base64.StdEncoding.EncodeToString([]byte(pubKey))
config = &Config{
AuthType: TypeAgentLocal,
ECPublicKeyPEM: encodedPubKey,
ECPrivateKeyPEM: encodedPrivateKey,
}

a, err = NewAuthenticator(config, nil, logrus.New(), nil)
Expect(err).ToNot(HaveOccurred())
_, ok = a.(*AgentLocalAuthenticator)
Expect(ok).To(BeTrue())

})
})