Skip to content

Commit

Permalink
Merge pull request #1355 from canonical/v3
Browse files Browse the repository at this point in the history
Merge v3 into feature-http-proxy
  • Loading branch information
kian99 committed Sep 5, 2024
2 parents 5524b15 + 0ccb303 commit ff960e3
Show file tree
Hide file tree
Showing 95 changed files with 5,276 additions and 761 deletions.
15 changes: 13 additions & 2 deletions .github/actions/test-server/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,21 @@ runs:
username: ${{ github.actor }}
password: ${{ inputs.ghcr-pat }}

# We can't use a make target here because a composite action
# doesn't have a .git folder when checked out.
- name: Start server based on released version
if: ${{ inputs.jimm-version != 'dev' }}
run: make integration-test-env
run: |
cd local/traefik/certs; ./certs.sh; cd - && \
docker compose --profile test up -d --wait
shell: bash
working-directory: ${{ github.action_path }}/../../..
env:
JIMM_VERSION: ${{ inputs.jimm-version }}

- name: Start server based on development version
if: ${{ inputs.jimm-version == 'dev' }}
working-directory: ${{ github.action_path }}/../../..
run: make dev-env
shell: bash

Expand All @@ -59,6 +65,7 @@ runs:
echo 'jimm-ca<<EOF' >> $GITHUB_OUTPUT
cat ./local/traefik/certs/ca.crt >> $GITHUB_OUTPUT
echo 'EOF' >> $GITHUB_OUTPUT
working-directory: ${{ github.action_path }}/../../..
shell: bash

- name: Initialise LXD
Expand All @@ -73,6 +80,7 @@ runs:
- name: Setup cloud-init script for bootstraping Juju controllers
run: ./local/jimm/setup-controller.sh
shell: bash
working-directory: ${{ github.action_path }}/../../..
env:
SKIP_BOOTSTRAP: true
CLOUDINIT_FILE: "cloudinit.temp.yaml"
Expand All @@ -83,7 +91,7 @@ runs:
provider: "lxd"
channel: "5.19/stable"
juju-channel: ${{ inputs.juju-channel }}
bootstrap-options: "--config cloudinit.temp.yaml --config login-token-refresh-url=https://jimm.localhost/.well-known/jwks.json"
bootstrap-options: "--config ${{ github.action_path }}/../../../cloudinit.temp.yaml --config login-token-refresh-url=https://jimm.localhost/.well-known/jwks.json"

# As described in https://github.com/charmed-kubernetes/actions-operator grab the newly setup controller name
- name: Save LXD controller name
Expand All @@ -100,18 +108,21 @@ runs:

- name: Authenticate Juju CLI
run: chmod -R 666 ~/.local/share/juju/*.yaml && ./local/jimm/setup-cli-auth.sh
working-directory: ${{ github.action_path }}/../../..
shell: bash
# Below is a hardcoded JWT using the same test-secret used in JIMM's docker compose and allows the CLI to authenticate as the [email protected] user.
env:
JWT: ZXlKMGVYQWlPaUpLVjFRaUxDSmhiR2NpT2lKSVV6STFOaUo5LmV5SnBjM01pT2lKUGJteHBibVVnU2xkVUlFSjFhV3hrWlhJaUxDSnBZWFFpT2pFM01qUXlNamcyTmpBc0ltVjRjQ0k2TXprMk5EYzFNelEyTUN3aVlYVmtJam9pYW1sdGJTSXNJbk4xWWlJNkltcHBiVzB0ZEdWemRFQmpZVzV2Ym1sallXd3VZMjl0SW4wLkpTWVhXcGF6T0FnX1VFZ2hkbjlOZkVQdWxhWWlJQVdaX3BuSmRDbnJvWEk=

- name: Add LXD Juju controller to JIMM
run: ./local/jimm/add-controller.sh
working-directory: ${{ github.action_path }}/../../..
shell: bash
env:
JIMM_CONTROLLER_NAME: "jimm"
CONTROLLER_NAME: ${{ steps.lxd-controller.outputs.name }}

- name: Provide service account with cloud-credentials
run: ./local/jimm/setup-service-account.sh
working-directory: ${{ github.action_path }}/../../..
shell: bash
2 changes: 1 addition & 1 deletion .github/workflows/golangci-lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ permissions:
jobs:
golangci:
name: Lint
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4
Expand Down
8 changes: 3 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,10 @@ simplify:
gofmt -w -l -s .

# Generate version information
version/commit.txt: FORCE
version/commit.txt:
git rev-parse --verify HEAD > version/commit.txt

version/version.txt: FORCE
version/version.txt:
if [ -z "$(GIT_VERSION)" ]; then \
echo "dev" > version/version.txt; \
else \
Expand Down Expand Up @@ -155,6 +155,4 @@ help:
@echo 'make rock - Build the JIMM rock.'
@echo 'make load-rock - Load the most recently built rock into your local docker daemon.'

.PHONY: build check install release clean format server simplify sys-deps help FORCE

FORCE:
.PHONY: build check install release clean format server simplify sys-deps help
9 changes: 9 additions & 0 deletions charms/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
# JIMM Charms

Charms are a packaging tool for deploying application using Juju.
See more on charms [here](https://juju.is/charms-architecture).

JIMM has a machine charm and a Kubernetes charm for deploying the
application on different substrates. Currently the machine charm
is not maintained in favor of the K8s charm.

The machine charm has not yet been moved to a separate repo.

## K8S Charm
The K8S charm can be found [here](https://github.com/canonical/jimm-k8s-operator/).
8 changes: 7 additions & 1 deletion cmd/jimmctl/cmd/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,9 @@ type listGroupsCommand struct {

store jujuclient.ClientStore
dialOpts *jujuapi.DialOpts

limit int
offset int
}

// Info implements the cmd.Command interface.
Expand All @@ -349,6 +352,8 @@ func (c *listGroupsCommand) SetFlags(f *gnuflag.FlagSet) {
"yaml": cmd.FormatYaml,
"json": cmd.FormatJson,
})
f.IntVar(&c.limit, "limit", 0, "The maximum number of groups to return")
f.IntVar(&c.offset, "offset", 0, "The offset to use when requesting groups")
}

// Run implements Command.Run.
Expand All @@ -364,7 +369,8 @@ func (c *listGroupsCommand) Run(ctxt *cmd.Context) error {
}

client := api.NewClient(apiCaller)
groups, err := client.ListGroups()
req := apiparams.ListGroupsRequest{Limit: c.limit, Offset: c.offset}
groups, err := client.ListGroups(&req)
if err != nil {
return errors.E(err)
}
Expand Down
22 changes: 22 additions & 0 deletions cmd/jimmctl/cmd/group_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ import (

"github.com/juju/cmd/v3/cmdtesting"
gc "gopkg.in/check.v1"
"gopkg.in/yaml.v3"

"github.com/canonical/jimm/v3/cmd/jimmctl/cmd"
"github.com/canonical/jimm/v3/internal/cmdtest"
"github.com/canonical/jimm/v3/internal/dbmodel"
"github.com/canonical/jimm/v3/internal/jimmtest"
"github.com/canonical/jimm/v3/pkg/api/params"
)

type groupSuite struct {
Expand Down Expand Up @@ -116,6 +118,26 @@ func (s *groupSuite) TestListGroupsSuperuser(c *gc.C) {
c.Assert(strings.Contains(output, "test-group2"), gc.Equals, true)
}

func (s *groupSuite) TestListGroupsLimitSuperuser(c *gc.C) {
// alice is superuser
bClient := jimmtest.NewUserSessionLogin(c, "alice")

for i := 0; i < 3; i++ {
_, err := s.JimmCmdSuite.JIMM.Database.AddGroup(context.TODO(), fmt.Sprint("test-group", i))
c.Assert(err, gc.IsNil)
}

ctx, err := cmdtesting.RunCommand(c, cmd.NewListGroupsCommandForTesting(s.ClientStore(), bClient), "test-group", "--limit", "1", "--offset", "1")
c.Assert(err, gc.IsNil)
output := cmdtesting.Stdout(ctx)
groups := []params.Group{}
err = yaml.Unmarshal([]byte(output), &groups)
c.Assert(err, gc.IsNil)
c.Assert(groups, gc.HasLen, 1)
c.Assert(groups[0].Name, gc.Equals, "test-group1")
c.Assert(groups[0].UUID, gc.Not(gc.Equals), "")
}

func (s *groupSuite) TestListGroups(c *gc.C) {
// bob is not superuser
bClient := jimmtest.NewUserSessionLogin(c, "bob")
Expand Down
6 changes: 3 additions & 3 deletions cmd/jimmctl/cmd/relation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ func (s *relationSuite) TestRemoveRelationViaFileSuperuser(c *gc.C) {
func (s *relationSuite) TestRemoveRelation(c *gc.C) {
// bob is not superuser
bClient := jimmtest.NewUserSessionLogin(c, "bob")
_, err := cmdtesting.RunCommand(c, cmd.NewRemoveRelationCommandForTesting(s.ClientStore(), bClient), "test-group1#member", "member", "test-group2")
_, err := cmdtesting.RunCommand(c, cmd.NewRemoveRelationCommandForTesting(s.ClientStore(), bClient), "group-testGroup1#member", "member", "group-testGroup2")
c.Assert(err, gc.ErrorMatches, `unauthorized \(unauthorized access\)`)
}

Expand Down Expand Up @@ -440,9 +440,9 @@ func (s *relationSuite) TestListRelationsWithError(c *gc.C) {

ctx := context.Background()
group := &dbmodel.GroupEntry{Name: "group-1"}
err = s.JIMM.DB().GetGroup(ctx, group)
err = s.JIMM.Database.GetGroup(ctx, group)
c.Assert(err, gc.IsNil)
err = s.JIMM.DB().RemoveGroup(ctx, group)
err = s.JIMM.Database.RemoveGroup(ctx, group)
c.Assert(err, gc.IsNil)

expectedData := apiparams.ListRelationshipTuplesResponse{
Expand Down
19 changes: 11 additions & 8 deletions cmd/jimmsrv/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ func start(ctx context.Context, s *service.Service) error {
return errors.E("jimm session store secret must be at least 64 characters")
}

corsAllowedOrigins := strings.Split(os.Getenv("CORS_ALLOWED_ORIGINS"), " ")

jimmsvc, err := jimmsvc.NewService(ctx, jimmsvc.Params{
ControllerUUID: os.Getenv("JIMM_UUID"),
DSN: os.Getenv("JIMM_DSN"),
Expand All @@ -167,17 +169,18 @@ func start(ctx context.Context, s *service.Service) error {
JWTExpiryDuration: jwtExpiryDuration,
InsecureSecretStorage: insecureSecretStorage,
OAuthAuthenticatorParams: jimmsvc.OAuthAuthenticatorParams{
IssuerURL: issuerURL,
ClientID: clientID,
ClientSecret: clientSecret,
Scopes: scopesParsed,
SessionTokenExpiry: sessionTokenExpiryDuration,
SessionCookieMaxAge: sessionCookieMaxAgeInt,
JWTSessionKey: sessionSecretKey,
IssuerURL: issuerURL,
ClientID: clientID,
ClientSecret: clientSecret,
Scopes: scopesParsed,
SessionTokenExpiry: sessionTokenExpiryDuration,
SessionCookieMaxAge: sessionCookieMaxAgeInt,
JWTSessionKey: sessionSecretKey,
SecureSessionCookies: secureSessionCookies,
},
DashboardFinalRedirectURL: os.Getenv("JIMM_DASHBOARD_FINAL_REDIRECT_URL"),
SecureSessionCookies: secureSessionCookies,
CookieSessionKey: []byte(sessionSecretKey),
CorsAllowedOrigins: corsAllowedOrigins,
})
if err != nil {
return err
Expand Down
32 changes: 27 additions & 5 deletions cmd/jimmsrv/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/juju/names/v5"
"github.com/juju/zaputil/zapctx"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/rs/cors"
"go.uber.org/zap"
"gorm.io/driver/postgres"
"gorm.io/gorm"
Expand All @@ -37,9 +38,11 @@ import (
"github.com/canonical/jimm/v3/internal/jujuapi"
"github.com/canonical/jimm/v3/internal/jujuclient"
"github.com/canonical/jimm/v3/internal/logger"
"github.com/canonical/jimm/v3/internal/middleware"
"github.com/canonical/jimm/v3/internal/openfga"
ofganames "github.com/canonical/jimm/v3/internal/openfga/names"
"github.com/canonical/jimm/v3/internal/pubsub"
"github.com/canonical/jimm/v3/internal/rebac_admin"
"github.com/canonical/jimm/v3/internal/vault"
"github.com/canonical/jimm/v3/internal/wellknownapi"
)
Expand Down Expand Up @@ -82,6 +85,10 @@ type OAuthAuthenticatorParams struct {
// SessionCookieMaxAge holds the max age for session cookies in seconds.
SessionCookieMaxAge int

// SecureSessionCookies determines if HTTPS must be enabled in order for JIMM
// to set cookies when creating browser based sessions.
SecureSessionCookies bool

// JWTSessionKey holds the secret key used for signing/verifying JWT tokens.
// See internal/auth/oauth2.go AuthenticationService.SessionSecretkey for more details.
JWTSessionKey string
Expand Down Expand Up @@ -175,14 +182,14 @@ type Params struct {
// the /callback in an authorisation code OAuth2.0 flow to finish the flow.
DashboardFinalRedirectURL string

// SecureSessionCookies determines if HTTPS must be enabled in order for JIMM
// to set cookies when creating browser based sessions.
SecureSessionCookies bool

// CookieSessionKey is a randomly generated secret passed via config used for signing
// cookie data. The recommended length is 32/64 characters from the Gorilla securecookie lib.
// https://github.com/gorilla/securecookie/blob/main/securecookie.go#L124
CookieSessionKey []byte

// CorsAllowedOrigins represents all addresses that are valid for cross-origin
// requests. A wildcard '*' is accepted to allow all cross-origin requests.
CorsAllowedOrigins []string
}

// A Service is the implementation of a JIMM server.
Expand Down Expand Up @@ -347,6 +354,7 @@ func NewService(ctx context.Context, p Params) (*Service, error) {
SessionTokenExpiry: p.OAuthAuthenticatorParams.SessionTokenExpiry,
SessionCookieMaxAge: p.OAuthAuthenticatorParams.SessionCookieMaxAge,
JWTSessionKey: p.OAuthAuthenticatorParams.JWTSessionKey,
SecureCookies: p.OAuthAuthenticatorParams.SecureSessionCookies,
Store: &s.jimm.Database,
SessionStore: sessionStore,
RedirectURL: redirectUrl,
Expand Down Expand Up @@ -380,13 +388,28 @@ func NewService(ctx context.Context, p Params) (*Service, error) {
return nil, errors.E(op, err, "failed to parse final redirect url for the dashboard")
}

rebacBackend, err := rebac_admin.SetupBackend(ctx, &s.jimm)
if err != nil {
return nil, errors.E(op, err)
}

// Setup CORS middleware
corsOpts := cors.New(cors.Options{
AllowedOrigins: p.CorsAllowedOrigins,
AllowedMethods: []string{"GET"},
AllowCredentials: true,
})
s.mux.Use(corsOpts.Handler)

// Setup all HTTP handlers.
mountHandler := func(path string, h jimmhttp.JIMMHttpHandler) {
s.mux.Mount(path, h.Routes())
}

s.mux.Mount("/metrics", promhttp.Handler())

s.mux.Mount("/rebac", middleware.AuthenticateRebac(rebacBackend.Handler(""), &s.jimm))

mountHandler(
"/debug",
debugapi.NewDebugHandler(
Expand All @@ -406,7 +429,6 @@ func NewService(ctx context.Context, p Params) (*Service, error) {
oauthHandler, err := jimmhttp.NewOAuthHandler(jimmhttp.OAuthHandlerParams{
Authenticator: authSvc,
DashboardFinalRedirectURL: p.DashboardFinalRedirectURL,
SecureCookies: p.SecureSessionCookies,
})
if err != nil {
zapctx.Error(ctx, "failed to setup authentication handler", zap.Error(err))
Expand Down
Loading

0 comments on commit ff960e3

Please sign in to comment.