From 55709bb6bdd4a4ef17f6f99c3a3fa854607cfc4d Mon Sep 17 00:00:00 2001 From: Roger Peppe Date: Wed, 24 Jan 2018 12:51:47 +0000 Subject: [PATCH] httpbakery/agent: support authinfo in environment variables This makes it straightforward for client programs to support agent authentication without needing an explicit command line flag. --- httpbakery/agent/agent.go | 32 ++++++++++++++++++++++++++ httpbakery/agent/agent_test.go | 41 ++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/httpbakery/agent/agent.go b/httpbakery/agent/agent.go index 8850f61..66f6e03 100644 --- a/httpbakery/agent/agent.go +++ b/httpbakery/agent/agent.go @@ -7,8 +7,11 @@ package agent import ( + "encoding/json" "errors" + "io/ioutil" "net/url" + "os" "strings" "github.com/juju/loggo" @@ -40,6 +43,35 @@ type Agent struct { Username string `json:"username" yaml:"username"` } +var ErrNoAuthInfo = errgo.New("no bakery agent info found in environment") + +// AuthInfoFromEnvironment returns an AuthInfo derived +// from environment variables. +// +// It recognizes the following variable: +// BAKERY_AGENT_FILE - path to a file containing agent authentication +// info in JSON format (as marshaled by the AuthInfo type). +// +// If BAKERY_AGENT_FILE is not set, ErrNoAuthInfo will be returned. +func AuthInfoFromEnvironment() (*AuthInfo, error) { + agentFile := os.Getenv("BAKERY_AGENT_FILE") + if agentFile == "" { + return nil, errgo.WithCausef(nil, ErrNoAuthInfo, "") + } + var ai AuthInfo + data, err := ioutil.ReadFile(agentFile) + if err != nil { + return nil, errgo.Mask(err) + } + if err := json.Unmarshal(data, &ai); err != nil { + return nil, errgo.Notef(err, "cannot unmarshal agent information from %q: %v", agentFile) + } + if ai.Key == nil { + return nil, errgo.Newf("no private key found in %q", agentFile) + } + return &ai, nil +} + // SetUpAuth sets up agent authentication on the given client. // If this is called several times on the same client, earlier // calls will take precedence over later calls when there's diff --git a/httpbakery/agent/agent_test.go b/httpbakery/agent/agent_test.go index 21578fb..74459fb 100644 --- a/httpbakery/agent/agent_test.go +++ b/httpbakery/agent/agent_test.go @@ -1,9 +1,13 @@ package agent_test import ( + "encoding/json" + "io/ioutil" "net/http" + "os" "github.com/juju/testing" + jc "github.com/juju/testing/checkers" "golang.org/x/net/context" gc "gopkg.in/check.v1" "gopkg.in/errgo.v1" @@ -117,6 +121,43 @@ func (s *agentSuite) TestSetUpAuth(c *gc.C) { c.Assert(err, gc.Equals, nil) } +func (s *agentSuite) TestAuthInfoFromEnvironment(c *gc.C) { + defer os.Setenv("BAKERY_AGENT_FILE", "") + + f, err := ioutil.TempFile("", "") + c.Assert(err, gc.Equals, nil) + defer os.Remove(f.Name()) + defer f.Close() + + authInfo := &agent.AuthInfo{ + Key: bakery.MustGenerateKey(), + Agents: []agent.Agent{{ + URL: "https://0.1.2.3/x", + Username: "bob", + }, { + URL: "https://0.2.3.4", + Username: "charlie", + }}, + } + data, err := json.Marshal(authInfo) + _, err = f.Write(data) + c.Assert(err, gc.Equals, nil) + f.Close() + + os.Setenv("BAKERY_AGENT_FILE", f.Name()) + + authInfo1, err := agent.AuthInfoFromEnvironment() + c.Assert(err, gc.Equals, nil) + c.Assert(authInfo1, jc.DeepEquals, authInfo) +} + +func (s *agentSuite) TestAuthInfoFromEnvironmentNotSet(c *gc.C) { + os.Setenv("BAKERY_AGENT_FILE", "") + authInfo, err := agent.AuthInfoFromEnvironment() + c.Assert(errgo.Cause(err), gc.Equals, agent.ErrNoAuthInfo) + c.Assert(authInfo, gc.IsNil) +} + func AgentHandlers(h AgentHandler) []httprequest.Handler { return reqServer.Handlers(func(p httprequest.Params) (agentHandlers, context.Context, error) { return agentHandlers{h}, p.Context, nil