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