Skip to content

Commit

Permalink
Merge pull request juju#17729 from jack-w-shaw/JUJU-5862_implement_ma…
Browse files Browse the repository at this point in the history
…caroon_root_key_service

juju#17729

Implement macaroon root key service and backing state in DQLite

This service will later be used to construct a bakery.RootKeyStore. As such, the interfaces I have had to implement are nicely described here:

https://github.com/go-macaroon-bakery/macaroon-bakery/blob/f9b21e15a2ed91756aa172972c7178992c7fe6d1/bakery/dbrootkeystore/rootkey.go#L48-L95

Backing, however, only need to be stubs due to peculiarities with the bakery.RootKeyStore constructor.

As a flyby, rename bakery config test suites to be more specific.

## Checklist

- [x] Code style: imports ordered, good names, simple structure, etc
- [x] Comments saying why design decisions were made
- [x] Go unit tests, with comments saying what you're testing

## QA steps

```
go test github.com/juju/juju/domain/macaroon/...
```
  • Loading branch information
jujubot authored Jul 15, 2024
2 parents 2c5b2d1 + b0b4187 commit aaf758b
Show file tree
Hide file tree
Showing 13 changed files with 758 additions and 16 deletions.
16 changes: 16 additions & 0 deletions domain/macaroon/errors/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright 2024 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package errors

import "github.com/juju/errors"

const (
// KeyNotFound describes an error that occurs when a requested root key
// cannot be found
KeyNotFound = errors.ConstError("root key not found")

// KeyAlreadyExists describes an error that occurs when there is a clash
// when manipulating root keys
KeyAlreadyExists = errors.ConstError("root key already exists")
)
16 changes: 8 additions & 8 deletions domain/macaroon/service/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,20 @@ import (
gc "gopkg.in/check.v1"
)

type serviceSuite struct {
type configServiceSuite struct {
st *MockState
}

var _ = gc.Suite(&serviceSuite{})
var _ = gc.Suite(&configServiceSuite{})

func (s *serviceSuite) setupMocks(c *gc.C) *gomock.Controller {
func (s *configServiceSuite) setupMocks(c *gc.C) *gomock.Controller {
ctrl := gomock.NewController(c)

s.st = NewMockState(ctrl)
return ctrl
}

func (s *serviceSuite) TestInitialise(c *gc.C) {
func (s *configServiceSuite) TestInitialise(c *gc.C) {
defer s.setupMocks(c).Finish()

s.st.EXPECT().InitialiseBakeryConfig(
Expand All @@ -41,7 +41,7 @@ func (s *serviceSuite) TestInitialise(c *gc.C) {
c.Assert(err, jc.ErrorIsNil)
}

func (s *serviceSuite) TestGetLocalUsersKey(c *gc.C) {
func (s *configServiceSuite) TestGetLocalUsersKey(c *gc.C) {
defer s.setupMocks(c).Finish()

testKey := bakery.MustGenerateKey()
Expand All @@ -53,7 +53,7 @@ func (s *serviceSuite) TestGetLocalUsersKey(c *gc.C) {
c.Assert(key, gc.DeepEquals, testKey)
}

func (s *serviceSuite) TestGetLocalUsersThirdPartyKey(c *gc.C) {
func (s *configServiceSuite) TestGetLocalUsersThirdPartyKey(c *gc.C) {
defer s.setupMocks(c).Finish()

testKey := bakery.MustGenerateKey()
Expand All @@ -65,7 +65,7 @@ func (s *serviceSuite) TestGetLocalUsersThirdPartyKey(c *gc.C) {
c.Assert(key, gc.DeepEquals, testKey)
}

func (s *serviceSuite) TestGetExternalUsersThirdPartyKey(c *gc.C) {
func (s *configServiceSuite) TestGetExternalUsersThirdPartyKey(c *gc.C) {
defer s.setupMocks(c).Finish()

testKey := bakery.MustGenerateKey()
Expand All @@ -77,7 +77,7 @@ func (s *serviceSuite) TestGetExternalUsersThirdPartyKey(c *gc.C) {
c.Assert(key, gc.DeepEquals, testKey)
}

func (s *serviceSuite) TestGetOffersThirdPartyKey(c *gc.C) {
func (s *configServiceSuite) TestGetOffersThirdPartyKey(c *gc.C) {
defer s.setupMocks(c).Finish()

testKey := bakery.MustGenerateKey()
Expand Down
118 changes: 118 additions & 0 deletions domain/macaroon/service/package_mock_test.go

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

133 changes: 133 additions & 0 deletions domain/macaroon/service/rootkeys.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// Copyright 2024 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package service

import (
"context"
"time"

"github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery"
"github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery/dbrootkeystore"
"github.com/juju/errors"

"github.com/juju/juju/domain/macaroon"
macaroonerrors "github.com/juju/juju/domain/macaroon/errors"
)

// RootKeyState describes the persistence layer for
// macaroon root keys
type RootKeyState interface {
// GetKey gets the key with a given id from state. If not key is found, a
// macaroonerrors.KeyNotFound error is returned.
GetKey(ctx context.Context, id []byte) (macaroon.RootKey, error)

// FindLatestKey returns the most recently created root key k following all
// the conditions:
//
// k.Created >= createdAfter
// k.Expires >= expiresAfter
// k.Expires <= expiresBefore
//
// If no such key was found, return a macaroonerrors.KeyNotFound error
FindLatestKey(ctx context.Context, createdAfter, expiresAfter, expiresBefore time.Time) (macaroon.RootKey, error)

// InsertKey inserts the given root key into state. If a key with matching
// id already exists, return a macaroonerrors.KeyAlreadyExists error.
InsertKey(ctx context.Context, key macaroon.RootKey) error
}

// RootKeyService provides the API for macaroon root key storage
//
// RootKeyService satisfies dbrootkeystore.Backing and dbrootkeystore.ContextBacking
// https://github.com/go-macaroon-bakery/macaroon-bakery/blob/f9b21e15a2ed91756aa172972c7178992c7fe6d1/bakery/dbrootkeystore/rootkey.go#L48-L95
//
// This means RootKeyService can be used to construct a bakery.RootKeyStore.
//
// NOTE: We implement dbrootkeystore.Backing with stub methods. This is because
// RootKeyStore only requires a ContextBacking, but due to pecularities with
// the RootKeyStore constructor, we also need to implement Backing.
// TODO(jack-w-shaw): Once https://github.com/go-macaroon-bakery/macaroon-bakery/pull/301
// has been released, use NewContextStore & drop Backing stub methods
type RootKeyService struct {
st RootKeyState
}

// NewRootKeyService returns a new service for managing macaroon root keys
func NewRootKeyService(st RootKeyState) *RootKeyService {
return &RootKeyService{
st: st,
}
}

// GetKey is a stub method required to implement dbrootstore.Backing. Do not use
func (s *RootKeyService) GetKey(id []byte) (dbrootkeystore.RootKey, error) {
return dbrootkeystore.RootKey{}, errors.NotImplementedf("GetKey")
}

// GetKeyContext (dbrootkeystore.GetKeyContext) gets the key
// with a given id from dqlite.
//
// To satisfy dbrootkeystore.ContextBacking specification,
// if not key is found, a bakery.ErrNotFound error is returned.
func (s *RootKeyService) GetKeyContext(ctx context.Context, id []byte) (dbrootkeystore.RootKey, error) {
key, err := s.st.GetKey(ctx, id)
if errors.Is(err, macaroonerrors.KeyNotFound) {
return dbrootkeystore.RootKey{}, bakery.ErrNotFound
}
return decodeRootKey(key), nil
}

// FindLatestKey is a stub method required to implement dbrootstore.Backing. Do not use
func (s *RootKeyService) FindLatestKey(createdAfter, expiresAfter, expiresBefore time.Time) (dbrootkeystore.RootKey, error) {
return dbrootkeystore.RootKey{}, errors.NotImplementedf("FindLatestKey")
}

// FindLatestKeyContext (dbrootkeystore.FindLatestKeyContext) returns
// the most recently created root key k following all
// the conditions:
//
// k.Created >= createdAfter
// k.Expires >= expiresAfter
// k.Expires <= expiresBefore
//
// To satisfy dbrootkeystore.FindLatestKeyContext specification,
// if no such key is found, the zero root key is returned with a
// nil error
func (s *RootKeyService) FindLatestKeyContext(ctx context.Context, createdAfter, expiresAfter, expiresBefore time.Time) (dbrootkeystore.RootKey, error) {
key, err := s.st.FindLatestKey(ctx, createdAfter, expiresAfter, expiresBefore)
if errors.Is(err, macaroonerrors.KeyNotFound) {
return dbrootkeystore.RootKey{}, nil
}
return decodeRootKey(key), err
}

// InsertKey is a stub method required to implement dbrootstore.Backing. Do not use
func (s *RootKeyService) InsertKey(key dbrootkeystore.RootKey) error {
return errors.NotImplementedf("InsertKey")
}

// InsertKeyContext (dbrootkeystore.InsertKeyContext) inserts
// the given root key into state. If a key with matching
// id already exists, return a macaroonerrors.KeyAlreadyExists error.
func (s *RootKeyService) InsertKeyContext(ctx context.Context, key dbrootkeystore.RootKey) error {
return s.st.InsertKey(ctx, encodeRootKey(key))
}

func encodeRootKey(k dbrootkeystore.RootKey) macaroon.RootKey {
return macaroon.RootKey{
ID: k.Id,
Created: k.Created,
Expires: k.Expires,
RootKey: k.RootKey,
}
}

func decodeRootKey(k macaroon.RootKey) dbrootkeystore.RootKey {
return dbrootkeystore.RootKey{
Id: k.ID,
Created: k.Created,
Expires: k.Expires,
RootKey: k.RootKey,
}
}
Loading

0 comments on commit aaf758b

Please sign in to comment.