Skip to content

Commit

Permalink
JIMM uses JWT to log in to individual controllers.
Browse files Browse the repository at this point in the history
- removes the need for basic auth from JIMM as it will now use JWTs which Juju controllers trust
  • Loading branch information
alesstimec committed Sep 21, 2023
1 parent 1d1d295 commit f5e826b
Show file tree
Hide file tree
Showing 54 changed files with 970 additions and 724 deletions.
11 changes: 3 additions & 8 deletions api/params/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ type RemoveCloudFromControllerRequest struct {
// An AddControllerRequest is the request sent when adding a new controller
// to JIMM.
type AddControllerRequest struct {
// UUID of the controller.
UUID string `json:"uuid"`

// Name is the name to give to the controller, all controllers must
// have a unique name.
Name string `json:"name"`
Expand All @@ -49,14 +52,6 @@ type AddControllerRequest struct {
// connection to the controller. This is not needed if certificate is
// signed by a public CA.
CACertificate string `json:"ca-certificate,omitempty"`

// Username contains the username that JIMM should use to connect to
// the controller.
Username string `json:"username"`

// Password contains the password that JIMM should use to connect to
// the controller.
Password string `json:"password"`
}

// AuditLogAccessRequest is the request used to modify a user's access
Expand Down
5 changes: 2 additions & 3 deletions cmd/jimmctl/cmd/addcloudtocontroller.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"github.com/juju/cmd/v3"
"github.com/juju/gnuflag"
jujuapi "github.com/juju/juju/api"
"github.com/juju/juju/apiserver/common"
"github.com/juju/juju/cloud"
jujucmd "github.com/juju/juju/cmd"
jujucmdcloud "github.com/juju/juju/cmd/juju/cloud"
Expand All @@ -21,8 +20,8 @@ import (

"github.com/canonical/jimm/api"
apiparams "github.com/canonical/jimm/api/params"

"github.com/canonical/jimm/internal/errors"
jimmjujuapi "github.com/canonical/jimm/internal/jujuapi"
)

var (
Expand Down Expand Up @@ -157,7 +156,7 @@ func (c *addCloudToControllerCommand) addCloudToController(ctxt *cmd.Context, cl
ControllerName: c.dstControllerName,
AddCloudArgs: params.AddCloudArgs{
Name: c.cloudName,
Cloud: common.CloudToParams(*cloud),
Cloud: jimmjujuapi.CloudToParams(*cloud),
},
}

Expand Down
28 changes: 2 additions & 26 deletions cmd/jimmctl/cmd/addcloudtocontroller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"github.com/juju/cmd/v3/cmdtesting"
"github.com/juju/juju/cloud"
"github.com/juju/names/v4"

gc "gopkg.in/check.v1"

"github.com/canonical/jimm/cmd/jimmctl/cmd"
Expand Down Expand Up @@ -62,14 +61,10 @@ func (s *addCloudToControllerSuite) SetUpTest(c *gc.C) {
err = bob.SetCloudAccess(context.Background(), names.NewCloudTag("test-cloud"), ofganames.AdministratorRelation)
c.Assert(err, gc.IsNil)

// We add two controllers controller-1 and controller-2 using the
// test-cloud.
err = s.JIMM.Database.AddController(context.Background(), &dbmodel.Controller{
Name: "controller-1",
CACertificate: info.CACert,
AdminUser: info.Tag.Id(),
AdminPassword: info.Password,
UUID: "00000001-0000-0000-0000-000000000001",
UUID: info.ControllerUUID,
PublicAddress: info.Addrs[0],
CloudName: "test-cloud",
CloudRegion: "default",
Expand All @@ -80,26 +75,7 @@ func (s *addCloudToControllerSuite) SetUpTest(c *gc.C) {
})
c.Assert(err, gc.IsNil)

err = s.JIMM.OpenFGAClient.AddController(context.Background(), s.JIMM.ResourceTag(), names.NewControllerTag("00000001-0000-0000-0000-000000000001"))
c.Assert(err, gc.IsNil)

err = s.JIMM.Database.AddController(context.Background(), &dbmodel.Controller{
Name: "controller-2",
CACertificate: info.CACert,
AdminUser: info.Tag.Id(),
AdminPassword: info.Password,
UUID: "00000001-0000-0000-0000-000000000002",
PublicAddress: info.Addrs[0],
CloudName: "test-cloud",
CloudRegion: "default",
CloudRegions: []dbmodel.CloudRegionControllerPriority{{
CloudRegion: *region,
Priority: 10,
}},
})
c.Assert(err, gc.IsNil)

err = s.JIMM.OpenFGAClient.AddController(context.Background(), s.JIMM.ResourceTag(), names.NewControllerTag("00000001-0000-0000-0000-000000000002"))
err = s.JIMM.OpenFGAClient.AddController(context.Background(), s.JIMM.ResourceTag(), names.NewControllerTag(info.ControllerUUID))
c.Assert(err, gc.IsNil)
}

Expand Down
5 changes: 1 addition & 4 deletions cmd/jimmctl/cmd/addcontroller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,9 @@ var _ = gc.Suite(&addControllerSuite{})
func (s *addControllerSuite) TestAddControllerSuperuser(c *gc.C) {
info := s.APIInfo(c)
params := apiparams.AddControllerRequest{
UUID: info.ControllerUUID,
Name: "controller-1",
CACertificate: info.CACert,
Username: info.Tag.Id(),
Password: info.Password,
APIAddresses: info.Addrs,
}
tmpdir, tmpfile := writeYAMLTempFile(c, params)
Expand Down Expand Up @@ -85,8 +84,6 @@ func (s *addControllerSuite) TestAddController(c *gc.C) {
params := apiparams.AddControllerRequest{
Name: "controller-1",
CACertificate: info.CACert,
Username: info.Tag.Id(),
Password: info.Password,
APIAddresses: info.Addrs,
}
tmpdir, tmpfile := writeYAMLTempFile(c, params)
Expand Down
12 changes: 4 additions & 8 deletions cmd/jimmctl/cmd/controllerinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
package cmd

import (
"io/ioutil"
"os"

"github.com/juju/cmd/v3"
"github.com/juju/errors"
Expand Down Expand Up @@ -89,15 +89,11 @@ func (c *controllerInfoCommand) Run(ctxt *cmd.Context) error {
if err != nil {
return errors.Mask(err)
}
account, err := c.store.AccountDetails(c.controllerName)
if err != nil {
return errors.Mask(err)
}

info := apiparams.AddControllerRequest{
UUID: controller.ControllerUUID,
Name: c.controllerName,
APIAddresses: controller.APIEndpoints,
Username: account.User,
Password: account.Password,
}

info.PublicAddress = c.publicAddress
Expand All @@ -116,7 +112,7 @@ func (c *controllerInfoCommand) Run(ctxt *cmd.Context) error {
if err != nil {
return errors.Mask(err)
}
err = ioutil.WriteFile(c.file.Path, data, 0666)
err = os.WriteFile(c.file.Path, data, 0666)
if err != nil {
return errors.Mask(err)
}
Expand Down
57 changes: 20 additions & 37 deletions cmd/jimmctl/cmd/controllerinfo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
package cmd_test

import (
"io/ioutil"
"os"
"path"

Expand All @@ -23,8 +22,9 @@ var _ = gc.Suite(&controllerInfoSuite{})
func (s *controllerInfoSuite) TestControllerInfo(c *gc.C) {
store := s.ClientStore()
store.Controllers["controller-1"] = jujuclient.ControllerDetails{
APIEndpoints: []string{"127.0.0.1:17070"},
PublicDNSName: "controller1.example.com",
ControllerUUID: "982b16d9-a945-4762-b684-fd4fd885aa11",
APIEndpoints: []string{"127.0.0.1:17070"},
PublicDNSName: "controller1.example.com",
CACert: `-----BEGIN CERTIFICATE-----
MIID/jCCAmagAwIBAgIVANxsMrzsXrdpjjUoxWQm1RCkmWcqMA0GCSqGSIb3DQEB
CwUAMCYxDTALBgNVBAoTBEp1anUxFTATBgNVBAMTDGp1anUgdGVzdGluZzAeFw0y
Expand All @@ -50,12 +50,7 @@ func (s *controllerInfoSuite) TestControllerInfo(c *gc.C) {
LQRNNlaY2ajLt0paowf/Xxb8
-----END CERTIFICATE-----`,
}
store.Accounts["controller-1"] = jujuclient.AccountDetails{
User: "test-user",
Password: "super-secret-password",
}

dir, err := ioutil.TempDir("", "controller-info-test")
dir, err := os.MkdirTemp("", "controller-info-test")
c.Assert(err, gc.Equals, nil)
defer os.RemoveAll(dir)

Expand All @@ -64,22 +59,22 @@ func (s *controllerInfoSuite) TestControllerInfo(c *gc.C) {
_, err = cmdtesting.RunCommand(c, cmd.NewControllerInfoCommandForTesting(store), "controller-1", fname, "controller1.example.com")
c.Assert(err, gc.IsNil)

data, err := ioutil.ReadFile(fname)
data, err := os.ReadFile(fname)
c.Assert(err, gc.IsNil)
c.Assert(string(data), gc.Matches, `api-addresses:
- 127.0.0.1:17070
name: controller-1
password: super-secret-password
public-address: controller1.example.com
username: test-user
uuid: 982b16d9-a945-4762-b684-fd4fd885aa11
`)
}

func (s *controllerInfoSuite) TestControllerInfoWithLocalFlag(c *gc.C) {
store := s.ClientStore()
store.Controllers["controller-1"] = jujuclient.ControllerDetails{
APIEndpoints: []string{"127.0.0.1:17070"},
PublicDNSName: "controller1.example.com",
ControllerUUID: "982b16d9-a945-4762-b684-fd4fd885aa11",
APIEndpoints: []string{"127.0.0.1:17070"},
PublicDNSName: "controller1.example.com",
CACert: `-----BEGIN CERTIFICATE-----
MIID/jCCAmagAwIBAgIVANxsMrzsXrdpjjUoxWQm1RCkmWcqMA0GCSqGSIb3DQEB
CwUAMCYxDTALBgNVBAoTBEp1anUxFTATBgNVBAMTDGp1anUgdGVzdGluZzAeFw0y
Expand All @@ -105,12 +100,8 @@ func (s *controllerInfoSuite) TestControllerInfoWithLocalFlag(c *gc.C) {
LQRNNlaY2ajLt0paowf/Xxb8
-----END CERTIFICATE-----`,
}
store.Accounts["controller-1"] = jujuclient.AccountDetails{
User: "test-user",
Password: "super-secret-password",
}

dir, err := ioutil.TempDir("", "controller-info-test")
dir, err := os.MkdirTemp("", "controller-info-test")
c.Assert(err, gc.Equals, nil)
defer os.RemoveAll(dir)

Expand All @@ -119,7 +110,7 @@ func (s *controllerInfoSuite) TestControllerInfoWithLocalFlag(c *gc.C) {
_, err = cmdtesting.RunCommand(c, cmd.NewControllerInfoCommandForTesting(store), "controller-1", fname, "--local")
c.Assert(err, gc.IsNil)

data, err := ioutil.ReadFile(fname)
data, err := os.ReadFile(fname)
c.Assert(err, gc.IsNil)
c.Assert(string(data), gc.Matches, `api-addresses:
- 127.0.0.1:17070
Expand Down Expand Up @@ -149,17 +140,17 @@ ca-certificate: |-
LQRNNlaY2ajLt0paowf/Xxb8
-----END CERTIFICATE-----
name: controller-1
password: super-secret-password
public-address: 127.0.0.1:17070
username: test-user
uuid: 982b16d9-a945-4762-b684-fd4fd885aa11
`)
}

func (s *controllerInfoSuite) TestControllerInfoMissingPublicAddressAndNoLocalFlag(c *gc.C) {
store := s.ClientStore()
store.Controllers["controller-1"] = jujuclient.ControllerDetails{
APIEndpoints: []string{"127.0.0.1:17070"},
PublicDNSName: "controller1.example.com",
ControllerUUID: "982b16d9-a945-4762-b684-fd4fd885aa11",
APIEndpoints: []string{"127.0.0.1:17070"},
PublicDNSName: "controller1.example.com",
CACert: `-----BEGIN CERTIFICATE-----
MIID/jCCAmagAwIBAgIVANxsMrzsXrdpjjUoxWQm1RCkmWcqMA0GCSqGSIb3DQEB
CwUAMCYxDTALBgNVBAoTBEp1anUxFTATBgNVBAMTDGp1anUgdGVzdGluZzAeFw0y
Expand All @@ -185,12 +176,7 @@ func (s *controllerInfoSuite) TestControllerInfoMissingPublicAddressAndNoLocalFl
LQRNNlaY2ajLt0paowf/Xxb8
-----END CERTIFICATE-----`,
}
store.Accounts["controller-1"] = jujuclient.AccountDetails{
User: "test-user",
Password: "super-secret-password",
}

dir, err := ioutil.TempDir("", "controller-info-test")
dir, err := os.MkdirTemp("", "controller-info-test")
c.Assert(err, gc.Equals, nil)
defer os.RemoveAll(dir)

Expand All @@ -203,8 +189,9 @@ func (s *controllerInfoSuite) TestControllerInfoMissingPublicAddressAndNoLocalFl
func (s *controllerInfoSuite) TestControllerInfoCannotProvideAddrAndLocalFlag(c *gc.C) {
store := s.ClientStore()
store.Controllers["controller-1"] = jujuclient.ControllerDetails{
APIEndpoints: []string{"127.0.0.1:17070"},
PublicDNSName: "controller1.example.com",
ControllerUUID: "982b16d9-a945-4762-b684-fd4fd885aa11",
APIEndpoints: []string{"127.0.0.1:17070"},
PublicDNSName: "controller1.example.com",
CACert: `-----BEGIN CERTIFICATE-----
MIID/jCCAmagAwIBAgIVANxsMrzsXrdpjjUoxWQm1RCkmWcqMA0GCSqGSIb3DQEB
CwUAMCYxDTALBgNVBAoTBEp1anUxFTATBgNVBAMTDGp1anUgdGVzdGluZzAeFw0y
Expand All @@ -230,12 +217,8 @@ func (s *controllerInfoSuite) TestControllerInfoCannotProvideAddrAndLocalFlag(c
LQRNNlaY2ajLt0paowf/Xxb8
-----END CERTIFICATE-----`,
}
store.Accounts["controller-1"] = jujuclient.AccountDetails{
User: "test-user",
Password: "super-secret-password",
}

dir, err := ioutil.TempDir("", "controller-info-test")
dir, err := os.MkdirTemp("", "controller-info-test")
c.Assert(err, gc.Equals, nil)
defer os.RemoveAll(dir)

Expand Down
45 changes: 28 additions & 17 deletions cmd/jimmctl/cmd/importmodel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import (
"context"

"github.com/juju/cmd/v3/cmdtesting"
jjcloud "github.com/juju/juju/cloud"
jujuparams "github.com/juju/juju/rpc/params"
"github.com/juju/juju/testing/factory"
"github.com/juju/names/v4"
gc "gopkg.in/check.v1"

Expand All @@ -25,43 +27,52 @@ func (s *importModelSuite) TestImportModelSuperuser(c *gc.C) {
s.AddController(c, "controller-1", s.APIInfo(c))

cct := names.NewCloudCredentialTag(jimmtest.TestCloudName + "/charlie@external/cred")
s.UpdateCloudCredential(c, cct, jujuparams.CloudCredential{AuthType: "empty"})
mt := s.AddModel(c, names.NewUserTag("charlie@external"), "model-2", names.NewCloudTag(jimmtest.TestCloudName), jimmtest.TestCloudRegionName, cct)
var model dbmodel.Model
model.SetTag(mt)
err := s.JIMM.Database.GetModel(context.Background(), &model)
c.Assert(err, gc.Equals, nil)
err = s.JIMM.Database.DeleteModel(context.Background(), &model)
s.UpdateCloudCredential(c, cct, jujuparams.CloudCredential{AuthType: "empty", Attributes: map[string]string{"key": "value"}})

err := s.BackingState.UpdateCloudCredential(cct, jjcloud.NewCredential(jjcloud.EmptyAuthType, map[string]string{"key": "value"}))
c.Assert(err, gc.Equals, nil)

m := s.Factory.MakeModel(c, &factory.ModelParams{
Name: "model-2",
Owner: names.NewUserTag("charlie@external"),
CloudName: jimmtest.TestCloudName,
CloudRegion: jimmtest.TestCloudRegionName,
CloudCredential: cct,
})
defer m.Close()

// alice is superuser
bClient := s.userBakeryClient("alice")
_, err = cmdtesting.RunCommand(c, cmd.NewImportModelCommandForTesting(s.ClientStore(), bClient), "controller-1", mt.Id())
_, err = cmdtesting.RunCommand(c, cmd.NewImportModelCommandForTesting(s.ClientStore(), bClient), "controller-1", m.ModelUUID())
c.Assert(err, gc.IsNil)

var model2 dbmodel.Model
model2.SetTag(mt)
model2.SetTag(names.NewModelTag(m.ModelUUID()))
err = s.JIMM.Database.GetModel(context.Background(), &model2)
c.Assert(err, gc.Equals, nil)
c.Check(model2.CreatedAt.After(model.CreatedAt), gc.Equals, true)
}

func (s *importModelSuite) TestImportModelUnauthorized(c *gc.C) {
s.AddController(c, "controller-1", s.APIInfo(c))

cct := names.NewCloudCredentialTag(jimmtest.TestCloudName + "/charlie@external/cred")
s.UpdateCloudCredential(c, cct, jujuparams.CloudCredential{AuthType: "empty"})
mt := s.AddModel(c, names.NewUserTag("charlie@external"), "model-2", names.NewCloudTag(jimmtest.TestCloudName), jimmtest.TestCloudRegionName, cct)
var model dbmodel.Model
model.SetTag(mt)
err := s.JIMM.Database.GetModel(context.Background(), &model)
c.Assert(err, gc.Equals, nil)
err = s.JIMM.Database.DeleteModel(context.Background(), &model)

err := s.BackingState.UpdateCloudCredential(cct, jjcloud.NewCredential(jjcloud.EmptyAuthType, map[string]string{"key": "value"}))
c.Assert(err, gc.Equals, nil)

m := s.Factory.MakeModel(c, &factory.ModelParams{
Name: "model-2",
Owner: names.NewUserTag("charlie@external"),
CloudName: jimmtest.TestCloudName,
CloudRegion: jimmtest.TestCloudRegionName,
CloudCredential: cct,
})
defer m.Close()

// bob is not superuser
bClient := s.userBakeryClient("bob")
_, err = cmdtesting.RunCommand(c, cmd.NewImportModelCommandForTesting(s.ClientStore(), bClient), "controller-1", mt.Id())
_, err = cmdtesting.RunCommand(c, cmd.NewImportModelCommandForTesting(s.ClientStore(), bClient), "controller-1", m.ModelUUID())
c.Assert(err, gc.ErrorMatches, `unauthorized \(unauthorized access\)`)
}

Expand Down
Loading

0 comments on commit f5e826b

Please sign in to comment.