github.com/safedep/dry@v0.0.0-20241016050132-a15651f0548b/apiguard/manage.go (about)

     1  package apiguard
     2  
     3  import (
     4  	"context"
     5  	"crypto/tls"
     6  	"fmt"
     7  	"net/http"
     8  	"os"
     9  
    10  	"github.com/antihax/optional"
    11  	swagger "github.com/safedep/dry/apiguard/tykgen"
    12  )
    13  
    14  // Contract for a management client
    15  type ManagementClient interface {
    16  	// Create a key in the API Guard. We will also generate a custom key and
    17  	// not depend on API Guard's key generation.
    18  	CreateKey(context.Context, KeyArgs) (ApiKey, error)
    19  
    20  	// List policies in the API Guard
    21  	ListPolicies(context.Context) ([]Policy, error)
    22  }
    23  
    24  type managementClient struct {
    25  	baseUrl   string
    26  	token     string
    27  	client    http.Client
    28  	tykClient *swagger.APIClient
    29  	keyGen    KeyGen
    30  }
    31  
    32  type ManagementClientOpts func(*managementClient)
    33  
    34  // Helper to standardize the creation of a management client from environment
    35  // based configuration
    36  func NewManagementClientFromEnvConfig() (ManagementClient, error) {
    37  	baseUrl := os.Getenv("APIGUARD_BASE_URL")
    38  	if baseUrl == "" {
    39  		return nil, fmt.Errorf("APIGUARD_BASE_URL is not set")
    40  	}
    41  
    42  	token := os.Getenv("APIGUARD_TOKEN")
    43  	if token == "" {
    44  		return nil, fmt.Errorf("APIGUARD_TOKEN is not set")
    45  	}
    46  
    47  	skipTlsVerify := os.Getenv("INSECURE_APIGUARD_SKIP_TLS_VERIFY") == "true"
    48  
    49  	httpClient := http.Client{}
    50  	if skipTlsVerify {
    51  		httpClient.Transport = &http.Transport{
    52  			TLSClientConfig: &tls.Config{InsecureSkipVerify: skipTlsVerify},
    53  		}
    54  	}
    55  
    56  	client, err := NewManagementClient(baseUrl, token,
    57  		WithHTTPClient(httpClient))
    58  	if err != nil {
    59  		return nil, fmt.Errorf("Failed to create management client: %v", err)
    60  	}
    61  
    62  	return client, nil
    63  }
    64  
    65  // NewManagementClient creates a new management client for the API Guard.
    66  func NewManagementClient(baseUrl, token string, opts ...ManagementClientOpts) (ManagementClient, error) {
    67  	if len(baseUrl) > 0 && baseUrl[len(baseUrl)-1] == '/' {
    68  		baseUrl = baseUrl[:len(baseUrl)-1]
    69  	}
    70  
    71  	client := &managementClient{
    72  		token:   token,
    73  		keyGen:  defaultKeyGen(),
    74  		baseUrl: baseUrl,
    75  		client:  http.Client{},
    76  	}
    77  
    78  	for _, opt := range opts {
    79  		opt(client)
    80  	}
    81  
    82  	tykClientConfig := swagger.NewConfiguration()
    83  	tykClientConfig.BasePath = baseUrl
    84  	tykClientConfig.AddDefaultHeader("x-tyk-authorization", token)
    85  	tykClientConfig.HTTPClient = &client.client
    86  
    87  	client.tykClient = swagger.NewAPIClient(tykClientConfig)
    88  	return client, nil
    89  }
    90  
    91  func WithHTTPClient(httpClient http.Client) ManagementClientOpts {
    92  	return func(c *managementClient) {
    93  		c.client = httpClient
    94  	}
    95  }
    96  
    97  func WithKeyGen(keyGen KeyGen) ManagementClientOpts {
    98  	return func(c *managementClient) {
    99  		c.keyGen = keyGen
   100  	}
   101  }
   102  
   103  func (c *managementClient) CreateKey(ctx context.Context, args KeyArgs) (ApiKey, error) {
   104  	sessionState := swagger.SessionState{
   105  		Tags:          args.Tags,
   106  		Alias:         args.Alias,
   107  		ApplyPolicyId: args.PolicyId,
   108  		ApplyPolicies: args.Policies,
   109  		Expires:       args.ExpiresAt.Unix(),
   110  		MetaData: map[string]interface{}{
   111  			"org_id":  args.Info.OrganizationID,
   112  			"team_id": args.Info.TeamID,
   113  			"user_id": args.Info.UserID,
   114  			"key_id":  args.Info.KeyID,
   115  		},
   116  	}
   117  
   118  	key, err := c.keyGen()
   119  	if err != nil {
   120  		return ApiKey{}, fmt.Errorf("failed to generate key: %w", err)
   121  	}
   122  
   123  	apiRes, res, err := c.tykClient.KeysApi.CreateCustomKey(ctx, key, &swagger.KeysApiCreateCustomKeyOpts{
   124  		Body: optional.NewInterface(&sessionState),
   125  	})
   126  
   127  	if err != nil {
   128  		return ApiKey{}, err
   129  	}
   130  
   131  	defer res.Body.Close()
   132  	if res.StatusCode != http.StatusOK {
   133  		return ApiKey{}, fmt.Errorf("unexpected status code: %d", res.StatusCode)
   134  	}
   135  
   136  	if apiRes.Key == "" {
   137  		return ApiKey{}, fmt.Errorf("API Guard did not return a key")
   138  	}
   139  
   140  	return ApiKey{
   141  		Key:       apiRes.Key,
   142  		KeyId:     apiRes.KeyHash,
   143  		ExpiresAt: args.ExpiresAt,
   144  	}, nil
   145  }
   146  
   147  func (c *managementClient) ListPolicies(ctx context.Context) ([]Policy, error) {
   148  	policies, res, err := c.tykClient.PoliciesApi.ListPolicies(ctx)
   149  	if err != nil {
   150  		return nil, err
   151  	}
   152  
   153  	defer res.Body.Close()
   154  	if res.StatusCode != http.StatusOK {
   155  		return nil, fmt.Errorf("unexpected status code: %d", res.StatusCode)
   156  	}
   157  
   158  	var result []Policy
   159  	for _, p := range policies {
   160  		policy := Policy{
   161  			InternalID:         p.InternalId,
   162  			ID:                 p.Id,
   163  			Name:               p.Name,
   164  			QuotaMax:           p.QuotaMax,
   165  			QuotaRenewalRate:   p.QuotaRenewalRate,
   166  			Rate:               p.Rate,
   167  			RateInterval:       p.Per,
   168  			ThrottleInterval:   p.ThrottleInterval,
   169  			ThrottleRetryLimit: p.ThrottleRetryLimit,
   170  			Active:             p.Active,
   171  			AccessRights:       make([]PolicyAccess, 0, len(p.AccessRights)),
   172  		}
   173  
   174  		for apiID, access := range p.AccessRights {
   175  			policy.AccessRights = append(policy.AccessRights, PolicyAccess{
   176  				ApiID:   apiID,
   177  				ApiName: access.ApiName,
   178  			})
   179  		}
   180  
   181  		result = append(result, policy)
   182  	}
   183  
   184  	return result, nil
   185  }