Skip to content

Commit

Permalink
Merge commit 'dadedbc63ea00eb66dac4d0f5902b0e07c605dec' into audit-ap…
Browse files Browse the repository at this point in the history
…iserver-merge-2.4

This merges all changes up to (and including) the merge commit for PR juju#8184.
  • Loading branch information
babbageclunk committed Jan 11, 2018
2 parents 9a7afee + dadedbc commit 51d57d8
Show file tree
Hide file tree
Showing 39 changed files with 1,134 additions and 292 deletions.
3 changes: 1 addition & 2 deletions api/apiclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import (
"gopkg.in/retry.v1"

"github.com/juju/juju/api/base"
"github.com/juju/juju/apiserver/observer"
"github.com/juju/juju/apiserver/params"
"github.com/juju/juju/network"
"github.com/juju/juju/rpc"
Expand Down Expand Up @@ -197,7 +196,7 @@ func Open(info *Info, opts DialOpts) (Connection, error) {
return nil, errors.Trace(err)
}

client := rpc.NewConn(jsoncodec.New(dialResult.conn), observer.None())
client := rpc.NewConn(jsoncodec.New(dialResult.conn), nil)
client.Start(ctx)

bakeryClient := opts.BakeryClient
Expand Down
2 changes: 1 addition & 1 deletion api/testing/fakeserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func FakeAPIServer(root interface{}) net.Conn {
c0, c1 := net.Pipe()
serverCodec := jsoncodec.NewNet(c1)
serverRPC := rpc.NewConn(serverCodec, nil)
serverRPC.Serve(root, nil)
serverRPC.Serve(root, nil, nil)
serverRPC.Start(context.TODO())
go func() {
<-serverRPC.Dead()
Expand Down
37 changes: 34 additions & 3 deletions apiserver/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/juju/juju/apiserver/facades/agent/presence"
"github.com/juju/juju/apiserver/observer"
"github.com/juju/juju/apiserver/params"
"github.com/juju/juju/core/auditlog"
"github.com/juju/juju/permission"
"github.com/juju/juju/rpc"
"github.com/juju/juju/rpc/rpcreflect"
Expand Down Expand Up @@ -51,12 +52,12 @@ func newAdminAPIV3(srv *Server, root *apiHandler, apiObserver observer.Observer)

// Admin returns an object that provides API access to methods that can be
// called even when not authenticated.
func (r *admin) Admin(id string) (*admin, error) {
func (a *admin) Admin(id string) (*admin, error) {
if id != "" {
// Safeguard id for possible future use.
return nil, common.ErrBadId
}
return r, nil
return a, nil
}

// Login logs in with the provided credentials. All subsequent requests on the
Expand Down Expand Up @@ -135,7 +136,15 @@ func (a *admin) login(req params.LoginRequest, loginVersion int) (params.LoginRe
modelTag = a.root.model.Tag().String()
}

a.root.rpcConn.ServeRoot(apiRoot, serverError)
auditRecorder, err := a.getAuditRecorder(req, authResult)
if err != nil {
return fail, errors.Trace(err)
}

recorderFactory := observer.NewRecorderFactory(
a.apiObserver, auditRecorder)

a.root.rpcConn.ServeRoot(apiRoot, recorderFactory, serverError)
return params.LoginResult{
Servers: params.FromNetworkHostsPorts(hostPorts),
ControllerTag: a.root.model.ControllerTag().String(),
Expand All @@ -147,6 +156,28 @@ func (a *admin) login(req params.LoginRequest, loginVersion int) (params.LoginRe
}, nil
}

func (a *admin) getAuditRecorder(req params.LoginRequest, authResult *authResult) (*auditlog.Recorder, error) {
if !authResult.userLogin || a.srv.auditLogger == nil {
return nil, nil
}
result, err := auditlog.NewRecorder(
a.srv.auditLogger,
auditlog.ConversationArgs{
Who: req.AuthTag,
What: req.CLIArgs,
When: a.srv.clock.Now(),
ModelName: a.root.model.Name(),
ModelUUID: a.root.model.UUID(),
ConnectionID: a.root.connectionID,
},
)
if err != nil {
logger.Errorf("couldn't add login to audit log: %+v", err)
return nil, errors.Trace(err)
}
return result, nil
}

type authResult struct {
tag names.Tag // nil if external user login
anonymousLogin bool
Expand Down
143 changes: 105 additions & 38 deletions apiserver/admin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ import (
"github.com/juju/juju/apiserver/common"
"github.com/juju/juju/apiserver/facades/client/controller"
"github.com/juju/juju/apiserver/params"
servertesting "github.com/juju/juju/apiserver/testing"
"github.com/juju/juju/constraints"
"github.com/juju/juju/core/auditlog"
jujutesting "github.com/juju/juju/juju/testing"
"github.com/juju/juju/network"
"github.com/juju/juju/permission"
Expand All @@ -41,12 +43,6 @@ type baseLoginSuite struct {
jujutesting.JujuConnSuite
}

type loginSuite struct {
baseLoginSuite
}

var _ = gc.Suite(&loginSuite{})

func (s *baseLoginSuite) SetUpTest(c *gc.C) {
s.JujuConnSuite.SetUpTest(c)
loggo.GetLogger("juju.apiserver").SetLogLevel(loggo.TRACE)
Expand All @@ -62,6 +58,43 @@ func (s *baseLoginSuite) newMachineAndServer(c *gc.C) (*api.Info, *apiserver.Ser
return info, srv
}

func (s *baseLoginSuite) loginHostPorts(c *gc.C, info *api.Info) (connectedAddr string, hostPorts [][]network.HostPort) {
st, err := api.Open(info, fastDialOpts)
c.Assert(err, jc.ErrorIsNil)
defer st.Close()
return st.Addr(), st.APIHostPorts()
}

func (s *baseLoginSuite) addMachine(c *gc.C, job state.MachineJob) (*state.Machine, string) {
machine, err := s.State.AddMachine("quantal", job)
c.Assert(err, jc.ErrorIsNil)
password, err := utils.RandomPassword()
c.Assert(err, jc.ErrorIsNil)
err = machine.SetPassword(password)
c.Assert(err, jc.ErrorIsNil)
err = machine.SetProvisioned("foo", "fake_nonce", nil)
c.Assert(err, jc.ErrorIsNil)
return machine, password
}

func (s *baseLoginSuite) openAPIWithoutLogin(c *gc.C, info0 *api.Info) api.Connection {
info := *info0
info.Tag = nil
info.Password = ""
info.SkipLogin = true
info.Macaroons = nil
st, err := api.Open(&info, fastDialOpts)
c.Assert(err, jc.ErrorIsNil)
s.AddCleanup(func(*gc.C) { st.Close() })
return st
}

type loginSuite struct {
baseLoginSuite
}

var _ = gc.Suite(&loginSuite{})

func (s *loginSuite) TestLoginWithInvalidTag(c *gc.C) {
info := s.APIInfo(c)
info.Tag = nil
Expand Down Expand Up @@ -210,13 +243,6 @@ func (s *loginSuite) TestLoginAddrs(c *gc.C) {
c.Assert(hostPorts, gc.DeepEquals, stateAPIHostPorts)
}

func (s *baseLoginSuite) loginHostPorts(c *gc.C, info *api.Info) (connectedAddr string, hostPorts [][]network.HostPort) {
st, err := api.Open(info, fastDialOpts)
c.Assert(err, jc.ErrorIsNil)
defer st.Close()
return st.Addr(), st.APIHostPorts()
}

func startNLogins(c *gc.C, n int, info *api.Info) (chan error, *sync.WaitGroup) {
errResults := make(chan error, 100)
var doneWG sync.WaitGroup
Expand Down Expand Up @@ -541,30 +567,6 @@ func (s *loginSuite) TestControllerMachineLoginDuringMaintenance(c *gc.C) {
c.Assert(st.Close(), jc.ErrorIsNil)
}

func (s *baseLoginSuite) addMachine(c *gc.C, job state.MachineJob) (*state.Machine, string) {
machine, err := s.State.AddMachine("quantal", job)
c.Assert(err, jc.ErrorIsNil)
password, err := utils.RandomPassword()
c.Assert(err, jc.ErrorIsNil)
err = machine.SetPassword(password)
c.Assert(err, jc.ErrorIsNil)
err = machine.SetProvisioned("foo", "fake_nonce", nil)
c.Assert(err, jc.ErrorIsNil)
return machine, password
}

func (s *baseLoginSuite) openAPIWithoutLogin(c *gc.C, info0 *api.Info) api.Connection {
info := *info0
info.Tag = nil
info.Password = ""
info.SkipLogin = true
info.Macaroons = nil
st, err := api.Open(&info, fastDialOpts)
c.Assert(err, jc.ErrorIsNil)
s.AddCleanup(func(*gc.C) { st.Close() })
return st
}

func (s *loginSuite) TestAnonymousModelLogin(c *gc.C) {
info, srv := newServer(c, s.StatePool)
defer assertStop(c, srv)
Expand Down Expand Up @@ -922,6 +924,71 @@ func (s *loginSuite) TestLoginUpdatesLastLoginAndConnection(c *gc.C) {
c.Assert(when.After(startTime), jc.IsTrue)
}

func (s *loginSuite) TestLoginAddsAuditConversation(c *gc.C) {
log := &servertesting.FakeAuditLog{}
cfg := defaultServerConfig(c)
cfg.AuditLogConfig.Enabled = true
cfg.AuditLog = log
info, srv := newServerWithConfig(c, s.StatePool, cfg)
defer assertStop(c, srv)

password := "shhh..."
user := s.Factory.MakeUser(c, &factory.UserParams{
Password: password,
})
conn := s.openAPIWithoutLogin(c, info)

var result params.LoginResult
request := &params.LoginRequest{
AuthTag: user.Tag().String(),
Credentials: password,
CLIArgs: "hey you guys",
}
err := conn.APICall("Admin", 3, "", "Login", request, &result)
c.Assert(err, jc.ErrorIsNil)
c.Assert(result.UserInfo, gc.NotNil)

log.CheckCallNames(c, "AddConversation")
convo := log.Calls()[0].Args[0].(auditlog.Conversation)
// Blank out unknown fields.
convo.ConversationID = "0123456789abcdef"
convo.ConnectionID = "something"
c.Assert(convo, gc.Equals, auditlog.Conversation{
Who: user.Tag().String(),
What: "hey you guys",
When: cfg.Clock.Now().Format(time.RFC3339),
ModelName: s.IAASModel.Name(),
ModelUUID: s.IAASModel.UUID(),
ConnectionID: "something",
ConversationID: "0123456789abcdef",
})
}

func (s *loginSuite) TestAuditLoggingFailurePreventsLogin(c *gc.C) {
log := &servertesting.FakeAuditLog{}
log.SetErrors(errors.Errorf("bad news bears"))
cfg := defaultServerConfig(c)
cfg.AuditLogConfig.Enabled = true
cfg.AuditLog = log
info, srv := newServerWithConfig(c, s.StatePool, cfg)
defer assertStop(c, srv)

password := "shhh..."
user := s.Factory.MakeUser(c, &factory.UserParams{
Password: password,
})
conn := s.openAPIWithoutLogin(c, info)

var result params.LoginResult
request := &params.LoginRequest{
AuthTag: user.Tag().String(),
Credentials: password,
CLIArgs: "hey you guys",
}
err := conn.APICall("Admin", 3, "", "Login", request, &result)
c.Assert(err, gc.ErrorMatches, "bad news bears")
}

var _ = gc.Suite(&macaroonLoginSuite{})

type macaroonLoginSuite struct {
Expand Down Expand Up @@ -1251,7 +1318,7 @@ func (s *migrationSuite) TestExportingModel(c *gc.C) {
}

type loginV3Suite struct {
loginSuite
baseLoginSuite
}

var _ = gc.Suite(&loginV3Suite{})
Expand Down
Loading

0 comments on commit 51d57d8

Please sign in to comment.