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 }