Skip to content

Commit

Permalink
Add permissions to JWT
Browse files Browse the repository at this point in the history
For certain method calls, we need to add additional permissions to the JWT:
e.g. when adding a model, we need to add cloud permission stating that the
user has permission to add-model on that cloud.
  • Loading branch information
alesstimec committed Dec 13, 2023
1 parent 67090f7 commit 6c5fe4f
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 8 deletions.
42 changes: 38 additions & 4 deletions internal/jimm/applicationoffer.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/canonical/jimm/internal/errors"
"github.com/canonical/jimm/internal/openfga"
ofganames "github.com/canonical/jimm/internal/openfga/names"
jimmnames "github.com/canonical/jimm/pkg/names"
)

// AddApplicationOfferParams holds parameters for the Offer method.
Expand Down Expand Up @@ -202,7 +203,15 @@ func (j *JIMM) GetApplicationOfferConsumeDetails(ctx context.Context, user *open
return errors.E(op, errors.CodeNotFound)
}

api, err := j.dial(ctx, &offer.Model.Controller, names.ModelTag{})
api, err := j.dial(
ctx,
&offer.Model.Controller,
names.ModelTag{},
permission{
resource: jimmnames.NewApplicationOfferTag(offer.UUID).String(),
relation: accessLevel,
},
)
if err != nil {
return errors.E(op, err)
}
Expand Down Expand Up @@ -312,7 +321,15 @@ func (j *JIMM) GetApplicationOffer(ctx context.Context, user *openfga.User, offe
// controller. The all-watcher events do not include enough
// information to reasonably keep the local database up-to-date,
// and it would be non-trivial to make it do so.
api, err := j.dial(ctx, &offer.Model.Controller, names.ModelTag{})
api, err := j.dial(
ctx,
&offer.Model.Controller,
names.ModelTag{},
permission{
resource: jimmnames.NewApplicationOfferTag(offer.UUID).String(),
relation: accessLevel,
},
)
if err != nil {
return nil, errors.E(op, err)
}
Expand Down Expand Up @@ -498,7 +515,15 @@ func (j *JIMM) UpdateApplicationOffer(ctx context.Context, controller *dbmodel.C
return nil
}

api, err := j.dial(ctx, controller, offer.Model.ResourceTag())
api, err := j.dial(
ctx,
&offer.Model.Controller,
names.ModelTag{},
permission{
resource: jimmnames.NewApplicationOfferTag(offer.UUID).String(),
relation: "admin",
},
)
if err != nil {
return errors.E(op, err)
}
Expand Down Expand Up @@ -769,7 +794,16 @@ func (j *JIMM) doApplicationOfferAdmin(ctx context.Context, user *openfga.User,
if !isOfferAdmin {
return errors.E(op, errors.CodeUnauthorized, "unauthorized")
}
api, err := j.dial(ctx, &offer.Model.Controller, names.ModelTag{})
// add offer admin claim
api, err := j.dial(
ctx,
&offer.Model.Controller,
names.ModelTag{},
permission{
resource: jimmnames.NewApplicationOfferTag(offer.UUID).String(),
relation: "admin",
},
)
if err != nil {
return errors.E(op, err)
}
Expand Down
19 changes: 16 additions & 3 deletions internal/jimm/jimm.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,21 +108,34 @@ type Authenticator interface {
Authenticate(ctx context.Context, req *jujuparams.LoginRequest) (*openfga.User, error)
}

type permission struct {
resource string
relation string
}

// dial dials the controller and model specified by the given Controller
// and ModelTag. If no Dialer has been configured then an error with a
// code of CodeConnectionFailed will be returned.
func (j *JIMM) dial(ctx context.Context, ctl *dbmodel.Controller, modelTag names.ModelTag) (API, error) {
func (j *JIMM) dial(ctx context.Context, ctl *dbmodel.Controller, modelTag names.ModelTag, permissons ...permission) (API, error) {
if j == nil || j.Dialer == nil {
return nil, errors.E(errors.CodeConnectionFailed, "no dialer configured")
}
return j.Dialer.Dial(ctx, ctl, modelTag, nil)
var permissionMap map[string]string
if len(permissons) > 0 {
permissionMap = make(map[string]string, len(permissons))
for _, p := range permissons {
permissionMap[p.resource] = p.relation
}
}

return j.Dialer.Dial(ctx, ctl, modelTag, permissionMap)
}

// A Dialer provides a connection to a controller.
type Dialer interface {
// Dial creates an API connection to a controller. If the given
// model-tag is non-zero the connection will be to that model,
// otherwise the connection is to the controller. After sucessfully
// otherwise the connection is to the controller. After successfully
// dialing the controller the UUID, AgentVersion and HostPorts fields
// in the given controller should be updated to the values provided
// by the controller.
Expand Down
10 changes: 9 additions & 1 deletion internal/jimm/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,15 @@ func (b *modelBuilder) CreateControllerModel() *modelBuilder {
return b
}

api, err := b.jimm.dial(b.ctx, b.controller, names.ModelTag{})
api, err := b.jimm.dial(
b.ctx,
b.controller,
names.ModelTag{},
permission{
resource: b.cloud.ResourceTag().String(),
relation: "add-model",
},
)
if err != nil {
b.err = errors.E(err)
return b
Expand Down

0 comments on commit 6c5fe4f

Please sign in to comment.