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

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions cmd/agentbasedinstaller/client/main.go
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
Expand Up @@ -760,7 +760,7 @@ func (b *bareMetalInventory) generateShortImageDownloadURL(infraEnvID, imageType
return b.generateShortImageDownloadURLByAPIKey(infraEnvID, imageType, version, arch)
case auth.TypeRHSSO:
return b.generateShortImageDownloadURLByToken(infraEnvID, imageType, version, arch, imageTokenKey)
case auth.TypeNone:
case auth.TypeNone, auth.TypeAgentLocal:
return b.generateShortImageDownloadURLByID(infraEnvID, imageType, version, arch)
}

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
4 changes: 3 additions & 1 deletion internal/cluster/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ 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.TypeNone:
case auth.TypeNone, auth.TypeAgentLocal:
// For the agent based installer, the token is externally created by agent based installer.
// Hence, it is fine to return and empty token here for TypeAgentLocal.
token = ""
default:
err = errors.Errorf("invalid authentication type %v", authType)
Expand Down
106 changes: 106 additions & 0 deletions pkg/auth/agent_local_authenticator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package auth

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

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

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
}
110 changes: 110 additions & 0 deletions pkg/auth/agent_local_authenticator_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
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)
encodedPubKey := base64.StdEncoding.EncodeToString([]byte(pubKey))

cfg := &Config{
ECPublicKeyPEM: encodedPubKey,
}

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

})
11 changes: 7 additions & 4 deletions pkg/auth/authenticator.go
Original file line number Diff line number Diff line change
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 Down Expand Up @@ -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)
default:
err = fmt.Errorf("invalid authenticator type %v", cfg.AuthType)
}
Expand Down
19 changes: 19 additions & 0 deletions pkg/auth/authenticator_test.go
Original file line number Diff line number Diff line change
@@ -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(pubKey).ToNot(BeEmpty())
Expect(privKey).ToNot(BeEmpty())
Expect(err).ToNot(HaveOccurred())
encodedPubKey := base64.StdEncoding.EncodeToString([]byte(pubKey))
config = &Config{
AuthType: TypeAgentLocal,
ECPublicKeyPEM: encodedPubKey,
}

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

})
})