Skip to content

Commit

Permalink
HMS-2694 feat: Host conf token MVP
Browse files Browse the repository at this point in the history
Implement minimal host conf token and signing JWK.

A private ECDSA key is genenerated from a hard-coded seed and the JWK is
stored in memory. In the future, the ECDSA key will be generated from a
random source and the JWK stored in the database.

The `GET /signing_keys` endpoint returns the public JWK.

The `POST /host-conf` endpoint now returns a signed token.

Signed-off-by: Christian Heimes <cheimes@redhat.com>
  • Loading branch information
tiran authored and frasertweedale committed Oct 11, 2023
1 parent 730b8ec commit 41f95c2
Show file tree
Hide file tree
Showing 18 changed files with 219 additions and 95 deletions.
27 changes: 27 additions & 0 deletions internal/handler/impl/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ import (
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"hash"
"io"
"time"

"github.com/lestrrat-go/jwx/v2/jwk"
"github.com/podengo-project/idmsvc-backend/internal/config"
"github.com/podengo-project/idmsvc-backend/internal/handler"
"github.com/podengo-project/idmsvc-backend/internal/infrastructure/token"
"github.com/podengo-project/idmsvc-backend/internal/interface/client"
"github.com/podengo-project/idmsvc-backend/internal/interface/interactor"
"github.com/podengo-project/idmsvc-backend/internal/interface/presenter"
Expand Down Expand Up @@ -42,6 +46,9 @@ type hostComponent struct {
// Application secrets
type appSecrets struct {
domainRegKey []byte
// TODO: store JWKs in database
signingKeys []jwk.Key
publicKeys []string
}

type application struct {
Expand Down Expand Up @@ -120,6 +127,26 @@ func getAppSecret(config *config.Config) (sec *appSecrets, err error) {
return nil, err
}

// TODO: temporary hack
var (
priv jwk.Key
pub jwk.Key
pubs []byte
)
expiration := time.Now().Add(90 * 24 * time.Hour)
if priv, err = token.GeneratePrivateJWK(expiration); err != nil {
return nil, err
}
sec.signingKeys = []jwk.Key{priv}

if pub, err = priv.PublicKey(); err != nil {
return nil, err
}
if pubs, err = json.Marshal(pub); err != nil {
return nil, err
}
sec.publicKeys = []string{string(pubs)}

return sec, nil

}
Expand Down
10 changes: 9 additions & 1 deletion internal/handler/impl/host_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ func (a *application) HostConf(
domain *model.Domain
output *public.HostConfResponse
options *interactor.HostConfOptions
hctoken public.HostToken
tx *gorm.DB
xrhid *identity.XRHID
)
Expand All @@ -48,13 +49,20 @@ func (a *application) HostConf(
); err != nil {
return err
}
if hctoken, err = a.host.repository.SignHostConfToken(
a.secrets.signingKeys,
options,
domain,
); err != nil {
return err
}

if tx.Commit(); tx.Error != nil {
return tx.Error
}

if output, err = a.host.presenter.HostConf(
domain,
domain, hctoken,
); err != nil {
return err
}
Expand Down
4 changes: 2 additions & 2 deletions internal/handler/impl/keys_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import (
)

func (a *application) GetSigningKeys(ctx echo.Context, params public.GetSigningKeysParams) error {
// TODO: Not Implemented
// TODO: hacky implementation
output := public.SigningKeysResponse{
Keys: []string{},
Keys: a.secrets.publicKeys,
}
return ctx.JSON(http.StatusOK, output)
}
32 changes: 25 additions & 7 deletions internal/infrastructure/token/jwk.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"crypto/rand"
"encoding/base64"
"fmt"
"math/big"
"time"

"github.com/lestrrat-go/jwx/v2/jwa"
Expand All @@ -15,14 +16,20 @@ import (

const KeyCurve = jwa.P256

// TODO: hard-coded ECDSA seed for testing
const seed = "dummtestkey1234"

// Generate a private key with additional properties
// alg: based on key type (ES256 for P256)
// exp: expiration time (Unix timestamp)
// kid: base64 SHA-256 thumbprint
// use: "sig"
func GeneratePrivateJWK(expiration time.Time) (jwk.Key, error) {
var crv elliptic.Curve
var alg jwa.SignatureAlgorithm
func GeneratePrivateJWK(expiration time.Time) (key jwk.Key, err error) {
var (
crv elliptic.Curve
alg jwa.SignatureAlgorithm
raw *ecdsa.PrivateKey
)

switch KeyCurve {
case jwa.P256:
Expand All @@ -32,12 +39,23 @@ func GeneratePrivateJWK(expiration time.Time) (jwk.Key, error) {
return nil, fmt.Errorf("Unsupported JWK curve %s", KeyCurve)
}

raw, err := ecdsa.GenerateKey(crv, rand.Reader)
if err != nil {
return nil, err
if len(seed) != 0 {
raw = &ecdsa.PrivateKey{}
raw.D = &big.Int{}
_, ok := raw.D.SetString(seed, 36)
if !ok {
panic("seed")
}
raw.PublicKey.Curve = crv
raw.PublicKey.X, raw.PublicKey.Y = crv.ScalarBaseMult(raw.D.Bytes())
} else {
raw, err = ecdsa.GenerateKey(crv, rand.Reader)
if err != nil {
return nil, err
}
}

key, err := jwk.FromRaw(raw)
key, err = jwk.FromRaw(raw)
if err != nil {
return nil, err
}
Expand Down
7 changes: 4 additions & 3 deletions internal/interface/interactor/host_interactor.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ package interactor

import (
"github.com/google/uuid"
"github.com/podengo-project/idmsvc-backend/internal/api/public"
api_public "github.com/podengo-project/idmsvc-backend/internal/api/public"
"github.com/redhatinsights/platform-go-middlewares/identity"
)

type HostConfOptions struct {
OrgId string
CommonName string
InventoryId uuid.UUID
Fqdn string
CommonName public.SubscriptionManagerId
InventoryId public.HostId
Fqdn public.Fqdn
DomainId *uuid.UUID
DomainName *string
DomainType *api_public.DomainType
Expand Down
2 changes: 1 addition & 1 deletion internal/interface/presenter/host_presenter.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ import (
)

type HostPresenter interface {
HostConf(domain *model.Domain) (*public.HostConfResponse, error)
HostConf(domain *model.Domain, token public.HostToken) (*public.HostConfResponse, error)
}
4 changes: 4 additions & 0 deletions internal/interface/repository/host_repository.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package repository

import (
"github.com/lestrrat-go/jwx/v2/jwk"
"github.com/podengo-project/idmsvc-backend/internal/api/public"
"github.com/podengo-project/idmsvc-backend/internal/domain/model"
"github.com/podengo-project/idmsvc-backend/internal/interface/interactor"
"gorm.io/gorm"
Expand All @@ -9,4 +11,6 @@ import (
// HostRepository interface
type HostRepository interface {
MatchDomain(db *gorm.DB, options *interactor.HostConfOptions) (output *model.Domain, err error)
// TODO: hack, actual implementation will take gorm.DB argument
SignHostConfToken(privs []jwk.Key, options *interactor.HostConfOptions, domain *model.Domain) (hctoken public.HostToken, err error)
}
18 changes: 9 additions & 9 deletions internal/test/mock/interface/presenter/host_presenter.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 27 additions & 1 deletion internal/test/mock/interface/repository/host_repository.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions internal/test/variables.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const (
)

var (
DomainType = public.RhelIdm
DomainUUID = uuid.MustParse(DomainId)
RealmDomains = []string{DomainName, "otherdomain.test"}
Location1 = public.Location{
Expand Down Expand Up @@ -225,6 +226,7 @@ func BuildDomainModel(orgID string) *model.Domain {
}

var (
SystemXRHID = GetSystemXRHID(OrgId, Server1.CertCN, SystemAccountNr)
UserXRHID = GetUserXRHID(OrgId, UserName, UserId, UserAccountNr, false)
SystemXRHID = GetSystemXRHID(OrgId, Server1.CertCN, SystemAccountNr)
Client1XRHID = GetSystemXRHID(OrgId, Client1.CertCN, SystemAccountNr)
UserXRHID = GetUserXRHID(OrgId, UserName, UserId, UserAccountNr, false)
)
15 changes: 13 additions & 2 deletions internal/usecase/interactor/host_interactor.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package interactor
import (
"fmt"

"github.com/google/uuid"
api_public "github.com/podengo-project/idmsvc-backend/internal/api/public"
internal_errors "github.com/podengo-project/idmsvc-backend/internal/errors"
"github.com/podengo-project/idmsvc-backend/internal/interface/interactor"
Expand All @@ -15,7 +16,13 @@ func NewHostInteractor() interactor.HostInteractor {
return hostInteractor{}
}

func (i hostInteractor) HostConf(xrhid *identity.XRHID, inventoryId api_public.HostId, fqdn string, params *api_public.HostConfParams, body *api_public.HostConf) (*interactor.HostConfOptions, error) {
func (i hostInteractor) HostConf(
xrhid *identity.XRHID,
inventoryId api_public.HostId,
fqdn api_public.Fqdn,
params *api_public.HostConfParams,
body *api_public.HostConf,
) (*interactor.HostConfOptions, error) {
if xrhid == nil {
return nil, internal_errors.NilArgError("xrhid")
}
Expand All @@ -31,10 +38,14 @@ func (i hostInteractor) HostConf(xrhid *identity.XRHID, inventoryId api_public.H
if body == nil {
return nil, internal_errors.NilArgError("body")
}
cn, err := uuid.Parse(xrhid.Identity.System.CommonName)
if err != nil {
return nil, err
}

options := &interactor.HostConfOptions{
OrgId: xrhid.Identity.OrgID,
CommonName: xrhid.Identity.System.CommonName,
CommonName: cn,
InventoryId: inventoryId,
Fqdn: fqdn,
DomainId: body.DomainId,
Expand Down
Loading

0 comments on commit 41f95c2

Please sign in to comment.