1package conjurapi 2 3import ( 4 "crypto/tls" 5 "crypto/x509" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "net/http" 10 "os" 11 "strings" 12 "time" 13 14 "github.com/bgentry/go-netrc/netrc" 15 "github.com/cyberark/conjur-api-go/conjurapi/authn" 16 "github.com/cyberark/conjur-api-go/conjurapi/logging" 17) 18 19type Authenticator interface { 20 RefreshToken() ([]byte, error) 21 NeedsTokenRefresh() bool 22} 23 24type Client struct { 25 config Config 26 authToken authn.AuthnToken 27 httpClient *http.Client 28 authenticator Authenticator 29 router Router 30} 31 32type Router interface { 33 AddSecretRequest(variableID, secretValue string) (*http.Request, error) 34 AuthenticateRequest(loginPair authn.LoginPair) (*http.Request, error) 35 CheckPermissionRequest(resourceID, privilege string) (*http.Request, error) 36 LoadPolicyRequest(mode PolicyMode, policyID string, policy io.Reader) (*http.Request, error) 37 ResourceRequest(resourceID string) (*http.Request, error) 38 ResourcesRequest(filter *ResourceFilter) (*http.Request, error) 39 RetrieveBatchSecretsRequest(variableIDs []string) (*http.Request, error) 40 RetrieveSecretRequest(variableID string) (*http.Request, error) 41 RotateAPIKeyRequest(roleID string) (*http.Request, error) 42} 43 44func NewClientFromKey(config Config, loginPair authn.LoginPair) (*Client, error) { 45 authenticator := &authn.APIKeyAuthenticator{ 46 LoginPair: loginPair, 47 } 48 client, err := newClientWithAuthenticator( 49 config, 50 authenticator, 51 ) 52 authenticator.Authenticate = client.Authenticate 53 return client, err 54} 55 56// ReadResponseBody fully reads a response and closes it. 57func ReadResponseBody(response io.ReadCloser) ([]byte, error) { 58 defer response.Close() 59 return ioutil.ReadAll(response) 60} 61 62func NewClientFromToken(config Config, token string) (*Client, error) { 63 return newClientWithAuthenticator( 64 config, 65 &authn.TokenAuthenticator{token}, 66 ) 67} 68 69func NewClientFromTokenFile(config Config, tokenFile string) (*Client, error) { 70 return newClientWithAuthenticator( 71 config, 72 &authn.TokenFileAuthenticator{ 73 TokenFile: tokenFile, 74 MaxWaitTime: -1, 75 }, 76 ) 77} 78 79func LoginPairFromEnv() (*authn.LoginPair, error) { 80 return &authn.LoginPair{ 81 Login: os.Getenv("CONJUR_AUTHN_LOGIN"), 82 APIKey: os.Getenv("CONJUR_AUTHN_API_KEY"), 83 }, nil 84} 85 86func LoginPairFromNetRC(config Config) (*authn.LoginPair, error) { 87 if config.NetRCPath == "" { 88 config.NetRCPath = os.ExpandEnv("$HOME/.netrc") 89 } 90 91 rc, err := netrc.ParseFile(config.NetRCPath) 92 if err != nil { 93 return nil, err 94 } 95 96 m := rc.FindMachine(config.ApplianceURL + "/authn") 97 98 if m == nil { 99 return nil, fmt.Errorf("No credentials found in NetRCPath") 100 } 101 102 return &authn.LoginPair{Login: m.Login, APIKey: m.Password}, nil 103} 104 105func NewClientFromEnvironment(config Config) (*Client, error) { 106 err := config.validate() 107 108 if err != nil { 109 return nil, err 110 } 111 112 authnTokenFile := os.Getenv("CONJUR_AUTHN_TOKEN_FILE") 113 if authnTokenFile != "" { 114 return NewClientFromTokenFile(config, authnTokenFile) 115 } 116 117 loginPair, err := LoginPairFromEnv() 118 if err == nil && loginPair.Login != "" && loginPair.APIKey != "" { 119 return NewClientFromKey(config, *loginPair) 120 } 121 122 loginPair, err = LoginPairFromNetRC(config) 123 if err == nil && loginPair.Login != "" && loginPair.APIKey != "" { 124 return NewClientFromKey(config, *loginPair) 125 } 126 127 return nil, fmt.Errorf("Environment variables and machine identity files satisfying at least one authentication strategy must be present!") 128} 129 130func (c *Client) GetHttpClient() (*http.Client) { 131 return c.httpClient 132} 133 134func (c *Client) SetHttpClient(httpClient *http.Client) { 135 c.httpClient = httpClient 136} 137 138func (c *Client) GetConfig() (Config) { 139 return c.config 140} 141 142func (c *Client) SubmitRequest(req *http.Request) (resp *http.Response, err error) { 143 err = c.createAuthRequest(req) 144 if err != nil { 145 return 146 } 147 148 logging.ApiLog.Debugf("req: %+v\n", req) 149 resp, err = c.httpClient.Do(req) 150 if err != nil { 151 return 152 } 153 154 return 155} 156 157func makeFullId(account, kind, id string) string { 158 tokens := strings.SplitN(id, ":", 3) 159 switch len(tokens) { 160 case 1: 161 tokens = []string{account, kind, tokens[0]} 162 case 2: 163 tokens = []string{account, tokens[0], tokens[1]} 164 } 165 return strings.Join(tokens, ":") 166} 167 168func parseID(fullID string) (account, kind, id string, err error) { 169 tokens := strings.SplitN(fullID, ":", 3) 170 if len(tokens) != 3 { 171 err = fmt.Errorf("Id '%s' must be fully qualified", fullID) 172 return 173 } 174 return tokens[0], tokens[1], tokens[2], nil 175} 176 177func newClientWithAuthenticator(config Config, authenticator Authenticator) (*Client, error) { 178 var ( 179 err error 180 ) 181 182 err = config.validate() 183 184 if err != nil { 185 return nil, err 186 } 187 188 var httpClient *http.Client 189 var router Router 190 191 if config.IsHttps() { 192 cert, err := config.ReadSSLCert() 193 if err != nil { 194 return nil, err 195 } 196 httpClient, err = newHTTPSClient(cert) 197 if err != nil { 198 return nil, err 199 } 200 } else { 201 httpClient = &http.Client{Timeout: time.Second * 10} 202 } 203 204 if config.V4 { 205 router = RouterV4{&config} 206 } else { 207 router = RouterV5{&config} 208 } 209 210 return &Client{ 211 config: config, 212 authenticator: authenticator, 213 httpClient: httpClient, 214 router: router, 215 }, nil 216} 217 218func newHTTPSClient(cert []byte) (*http.Client, error) { 219 pool := x509.NewCertPool() 220 ok := pool.AppendCertsFromPEM(cert) 221 if !ok { 222 return nil, fmt.Errorf("Can't append Conjur SSL cert") 223 } 224 tr := &http.Transport{ 225 TLSClientConfig: &tls.Config{RootCAs: pool}, 226 } 227 return &http.Client{Transport: tr, Timeout: time.Second * 10}, nil 228} 229