cmd/apmtool/credentials.go (95 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package main import ( "context" "encoding/json" "errors" "fmt" "os" "time" "github.com/urfave/cli/v3" ) type credentials struct { Expiry time.Time `json:"expiry,omitempty"` APIKey string `json:"api_key,omitempty"` SecretToken string `json:"secret_token,omitempty"` } // readCachedCredentials returns any cached credentials for the given URL. // If there are no cached credentials, readCachedCredentials returns an error // satisfying errors.Is(err, os.ErrNotExist). func readCachedCredentials(url string) (*credentials, error) { data, err := readCache("credentials.json") if err != nil { return nil, fmt.Errorf("error reading cached credentials: %w", err) } var m map[string]*credentials if err := json.Unmarshal(data, &m); err != nil { return nil, fmt.Errorf("error decoding cached credentials: %w", err) } if c, ok := m[url]; ok { return c, nil } return nil, fmt.Errorf("no credentials cached for %q: %w", url, os.ErrNotExist) } // updateCachedCredentials updates credentials for the given URL. // // Any expired credentials will be remove from the cache. func updateCachedCredentials(url string, c *credentials) error { if err := updateCache("credentials.json", func(data []byte) ([]byte, error) { m := make(map[string]*credentials) if data != nil { if err := json.Unmarshal(data, &m); err != nil { return nil, err } } m[url] = c now := time.Now() for k, v := range m { if !v.Expiry.IsZero() && v.Expiry.Before(now) { delete(m, k) } } return json.Marshal(m) }); err != nil { return fmt.Errorf("error updating cached credentials: %w", err) } return nil } func (cmd *Commands) getCredentials(ctx context.Context, c *cli.Command) (*credentials, error) { creds, err := readCachedCredentials(cmd.cfg.APMServerURL) if err == nil { return creds, nil } else if !errors.Is(err, os.ErrNotExist) { return nil, err } client, err := cmd.getClient() if err != nil { return nil, err } var expiry time.Time // First check if there's an Elastic Cloud integration policy, // and extract a secret token from that. Otherwise, create an // API Key. var apiKey, secretToken string policy, err := client.GetElasticCloudAPMInput(ctx) policyErr := fmt.Errorf("error getting APM cloud input: %w", err) if err != nil { if c.Bool("verbose") { fmt.Fprintln(os.Stderr, policyErr) } } else { secretToken = policy.Get("apm-server.auth.secret_token").String() } // Create an API Key. fmt.Fprintln(os.Stderr, "Creating agent API Key...") expiryDuration := c.Duration("api-key-expiration") if expiryDuration > 0 { expiry = time.Now().Add(expiryDuration) } apiKey, err = client.CreateAgentAPIKey(ctx, expiryDuration) if err != nil { apiKeyErr := err return nil, fmt.Errorf( "failed to obtain agent credentials: %w", errors.Join(apiKeyErr, policyErr), ) } creds = &credentials{ Expiry: expiry, APIKey: apiKey, SecretToken: secretToken, } if err := updateCachedCredentials(cmd.cfg.APMServerURL, creds); err != nil { return nil, err } return creds, nil }