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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 = "" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
}) | ||
|
||
}) |
There was a problem hiding this comment.
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?