Skip to content

Commit 2ae7e2c

Browse files
authored
Merge pull request #262 from jetstack/api-token
Implement API token authentication for upload requests
2 parents ec05e70 + 87dac33 commit 2ae7e2c

File tree

9 files changed

+452
-299
lines changed

9 files changed

+452
-299
lines changed

cmd/agent.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"io/ioutil"
66
"log"
7+
"os"
78
"time"
89

910
"github.com/jetstack/preflight/pkg/agent"
@@ -110,5 +111,10 @@ func init() {
110111
false,
111112
"Runs agent in strict mode. No retry attempts will be made for a missing data gatherer's data.",
112113
)
113-
114+
agentCmd.PersistentFlags().StringVar(
115+
&agent.APIToken,
116+
"api-token",
117+
os.Getenv("API_TOKEN"),
118+
"Token used for authentication when API tokens are in use on the backend",
119+
)
114120
}

pkg/agent/run.go

Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ var BackoffMaxTime time.Duration
4747
// StrictMode flag causes the agent to fail at the first attempt
4848
var StrictMode bool
4949

50+
// APIToken is an authentication token used for the backend API as an alternative to oauth flows.
51+
var APIToken string
52+
5053
// schema version of the data sent by the agent.
5154
// The new default version is v2.
5255
// In v2 the agent posts data readings using api.gathereredResources
@@ -59,7 +62,7 @@ const schemaVersion string = "v2.0.0"
5962
func Run(cmd *cobra.Command, args []string) {
6063
ctx, cancel := context.WithCancel(context.Background())
6164
defer cancel()
62-
config, preflightClient := getConfiguration(ctx)
65+
config, preflightClient := getConfiguration()
6366

6467
dataGatherers := map[string]datagatherer.DataGatherer{}
6568
var wg sync.WaitGroup
@@ -137,7 +140,7 @@ func Run(cmd *cobra.Command, args []string) {
137140
Period = config.Period
138141
}
139142

140-
gatherAndOutputData(ctx, config, preflightClient, dataGatherers)
143+
gatherAndOutputData(config, preflightClient, dataGatherers)
141144

142145
if OneShot {
143146
break
@@ -147,7 +150,7 @@ func Run(cmd *cobra.Command, args []string) {
147150
}
148151
}
149152

150-
func getConfiguration(ctx context.Context) (Config, *client.PreflightClient) {
153+
func getConfiguration() (Config, client.Client) {
151154
log.Printf("Preflight agent version: %s (%s)", version.PreflightVersion, version.Commit)
152155
file, err := os.Open(ConfigFilePath)
153156
if err != nil {
@@ -203,25 +206,28 @@ func getConfiguration(ctx context.Context) (Config, *client.PreflightClient) {
203206
Version: version.PreflightVersion,
204207
ClusterID: config.ClusterID,
205208
}
206-
var preflightClient *client.PreflightClient
207-
if credentials != nil {
208-
log.Printf("A credentials file was specified. Using OAuth2 authentication...")
209-
preflightClient, err = client.New(agentMetadata, credentials, baseURL)
210-
if err != nil {
211-
log.Fatalf("Error creating preflight client: %+v", err)
212-
}
213-
} else {
214-
log.Printf("No credentials file was specified. Starting client with no authentication...")
215-
preflightClient, err = client.NewWithNoAuth(agentMetadata, baseURL)
216-
if err != nil {
217-
log.Fatalf("Error creating preflight client: %+v", err)
218-
}
209+
210+
var preflightClient client.Client
211+
switch {
212+
case credentials != nil:
213+
log.Println("A credentials file was specified, using oauth authentication.")
214+
preflightClient, err = client.NewOAuthClient(agentMetadata, credentials, baseURL)
215+
case APIToken != "":
216+
log.Println("An API token was specified, using API token authentication.")
217+
preflightClient, err = client.NewAPITokenClient(agentMetadata, APIToken, baseURL)
218+
default:
219+
log.Println("No credentials were specified, using with no authentication.")
220+
preflightClient, err = client.NewUnauthenticatedClient(agentMetadata, baseURL)
221+
}
222+
223+
if err != nil {
224+
log.Fatalf("failed to create client: %v", err)
219225
}
220226

221227
return config, preflightClient
222228
}
223229

224-
func gatherAndOutputData(ctx context.Context, config Config, preflightClient *client.PreflightClient, dataGatherers map[string]datagatherer.DataGatherer) {
230+
func gatherAndOutputData(config Config, preflightClient client.Client, dataGatherers map[string]datagatherer.DataGatherer) {
225231
var readings []*api.DataReading
226232

227233
// Input/OutputPath flag overwrites agent.yaml configuration
@@ -243,7 +249,7 @@ func gatherAndOutputData(ctx context.Context, config Config, preflightClient *cl
243249
log.Fatalf("failed to unmarshal local data file: %s", err)
244250
}
245251
} else {
246-
readings = gatherData(ctx, config, dataGatherers)
252+
readings = gatherData(config, dataGatherers)
247253
}
248254

249255
if OutputPath != "" {
@@ -271,14 +277,7 @@ func gatherAndOutputData(ctx context.Context, config Config, preflightClient *cl
271277
}
272278
}
273279

274-
func startAndSyncDataGather(ctx context.Context, dg datagatherer.DataGatherer) error {
275-
if err := dg.Run(nil); err != nil {
276-
return err
277-
}
278-
return dg.WaitForCacheSync(nil)
279-
}
280-
281-
func gatherData(ctx context.Context, config Config, dataGatherers map[string]datagatherer.DataGatherer) []*api.DataReading {
280+
func gatherData(config Config, dataGatherers map[string]datagatherer.DataGatherer) []*api.DataReading {
282281
readings := []*api.DataReading{}
283282

284283
var dgError *multierror.Error
@@ -327,7 +326,7 @@ func gatherData(ctx context.Context, config Config, dataGatherers map[string]dat
327326
return readings
328327
}
329328

330-
func postData(config Config, preflightClient *client.PreflightClient, readings []*api.DataReading) error {
329+
func postData(config Config, preflightClient client.Client, readings []*api.DataReading) error {
331330
baseURL := config.Server
332331

333332
log.Println("Running Agent...")

pkg/client/accessToken.go

Lines changed: 0 additions & 88 deletions
This file was deleted.

pkg/client/client.go

Lines changed: 66 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,107 +1,98 @@
11
package client
22

33
import (
4-
"bytes"
54
"encoding/json"
65
"fmt"
7-
"io/ioutil"
8-
"path/filepath"
9-
"time"
6+
"io"
7+
"net/http"
8+
"strings"
109

10+
"github.com/hashicorp/go-multierror"
1111
"github.com/jetstack/preflight/api"
1212
)
1313

14-
// These variables are injected at build time.
15-
var ClientID string
16-
var ClientSecret string
17-
var AuthServerDomain string
14+
var (
15+
// ClientID is the auth0 client identifier (injected at build time)
16+
ClientID string
1817

19-
// PreflightClient can be used to talk to the Preflight backend.
20-
type PreflightClient struct {
21-
// OAuth2
22-
credentials *Credentials
23-
// accessToken is the current OAuth access token.
24-
accessToken *accessToken
18+
// ClientSecret is the auth0 client secret (injected at build time)
19+
ClientSecret string
2520

26-
baseURL string
27-
28-
agentMetadata *api.AgentMetadata
29-
}
21+
// AuthServerDomain is the auth0 domain (injected at build time)
22+
AuthServerDomain string
23+
)
3024

31-
// NewWithNoAuth creates a new client with no authentication.
32-
func NewWithNoAuth(agentMetadata *api.AgentMetadata, baseURL string) (*PreflightClient, error) {
33-
if baseURL == "" {
34-
return nil, fmt.Errorf("cannot create PreflightClient: baseURL cannot be empty")
25+
type (
26+
// The Client interface describes types that perform requests against the Jetstack Secure backend.
27+
Client interface {
28+
PostDataReadings(orgID, clusterID string, readings []*api.DataReading) error
29+
Post(path string, body io.Reader) (*http.Response, error)
3530
}
3631

37-
return &PreflightClient{
38-
agentMetadata: agentMetadata,
39-
baseURL: baseURL,
40-
}, nil
41-
}
42-
43-
// New creates a new client that uses OAuth2.
44-
func New(agentMetadata *api.AgentMetadata, credentials *Credentials, baseURL string) (*PreflightClient, error) {
45-
if err := credentials.validate(); err != nil {
46-
return nil, fmt.Errorf("cannot create PreflightClient: %v", err)
47-
}
48-
if baseURL == "" {
49-
return nil, fmt.Errorf("cannot create PreflightClient: baseURL cannot be empty")
32+
// Credentials defines the format of the credentials.json file.
33+
Credentials struct {
34+
// UserID is the ID or email for the user or service account.
35+
UserID string `json:"user_id"`
36+
// UserSecret is the secret for the user or service account.
37+
UserSecret string `json:"user_secret"`
38+
// The following fields are optional as the default behaviour
39+
// is to use the equivalent variables defined at package level
40+
// and injected at build time.
41+
// ClientID is the oauth2 client ID.
42+
ClientID string `json:"client_id,omitempty"`
43+
// ClientSecret is the oauth2 client secret.
44+
ClientSecret string `json:"client_secret,omitempty"`
45+
// AuthServerDomain is the domain for the auth server.
46+
AuthServerDomain string `json:"auth_server_domain,omitempty"`
5047
}
48+
)
5149

52-
if !credentials.IsClientSet() {
53-
credentials.ClientID = ClientID
54-
credentials.ClientSecret = ClientSecret
55-
credentials.AuthServerDomain = AuthServerDomain
50+
// ParseCredentials reads credentials into a struct used. Performs validations.
51+
func ParseCredentials(data []byte) (*Credentials, error) {
52+
var credentials Credentials
53+
54+
err := json.Unmarshal(data, &credentials)
55+
if err != nil {
56+
return nil, err
5657
}
5758

58-
if !credentials.IsClientSet() {
59-
return nil, fmt.Errorf("cannot create PreflightClient: invalid OAuth2 client configuration")
59+
if err = credentials.validate(); err != nil {
60+
return nil, err
6061
}
6162

62-
return &PreflightClient{
63-
agentMetadata: agentMetadata,
64-
credentials: credentials,
65-
baseURL: baseURL,
66-
accessToken: &accessToken{},
67-
}, nil
63+
return &credentials, nil
6864
}
6965

70-
func (c *PreflightClient) usingOAuth2() bool {
71-
if c.credentials == nil {
72-
return false
73-
}
74-
75-
return c.credentials.UserID != ""
66+
// IsClientSet returns whether the client credentials are set or not.
67+
func (c *Credentials) IsClientSet() bool {
68+
return c.ClientID != "" && c.ClientSecret != "" && c.AuthServerDomain != ""
7669
}
7770

78-
// PostDataReadings sends a slice of readings to Preflight.
79-
func (c *PreflightClient) PostDataReadings(orgID, clusterID string, readings []*api.DataReading) error {
80-
payload := api.DataReadingsPost{
81-
AgentMetadata: c.agentMetadata,
82-
DataGatherTime: time.Now().UTC(),
83-
DataReadings: readings,
71+
func (c *Credentials) validate() error {
72+
var result *multierror.Error
73+
74+
if c == nil {
75+
return fmt.Errorf("credentials are nil")
8476
}
85-
data, err := json.Marshal(payload)
86-
if err != nil {
87-
return err
77+
78+
if c.UserID == "" {
79+
result = multierror.Append(result, fmt.Errorf("user_id cannot be empty"))
8880
}
8981

90-
res, err := c.Post(filepath.Join("/api/v1/org", orgID, "datareadings", clusterID), bytes.NewBuffer(data))
91-
if err != nil {
92-
return err
82+
if c.UserSecret == "" {
83+
result = multierror.Append(result, fmt.Errorf("user_secret cannot be empty"))
9384
}
9485

95-
if code := res.StatusCode; code < 200 || code >= 300 {
96-
errorContent := ""
97-
body, err := ioutil.ReadAll(res.Body)
98-
if err == nil {
99-
errorContent = string(body)
100-
}
101-
defer res.Body.Close()
86+
return result.ErrorOrNil()
87+
}
10288

103-
return fmt.Errorf("received response with status code %d. Body: %s", code, errorContent)
89+
func fullURL(baseURL, path string) string {
90+
base := baseURL
91+
for strings.HasSuffix(base, "/") {
92+
base = strings.TrimSuffix(base, "/")
10493
}
105-
106-
return nil
94+
for strings.HasPrefix(path, "/") {
95+
path = strings.TrimPrefix(path, "/")
96+
}
97+
return fmt.Sprintf("%s/%s", base, path)
10798
}

0 commit comments

Comments
 (0)