diff --git a/README.md b/README.md index 37a3e47..1cdd1c7 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,7 @@ Flags: --log-format string The output format for logs: json, console ($BATON_LOG_FORMAT) (default "json") --log-level string The log level: debug, info, warn, error ($BATON_LOG_LEVEL) (default "info") -p, --provisioning This must be set in order for provisioning actions to be enabled. ($BATON_PROVISIONING) + --region string API region. Default is US. In case of EU based organization, pass region as EU. ($BATON_REGION) (default "US") -v, --version version for baton-verkada Use "baton-verkada [command] --help" for more information about a command. diff --git a/cmd/baton-verkada/config.go b/cmd/baton-verkada/config.go index b97fc0b..dcf4d29 100644 --- a/cmd/baton-verkada/config.go +++ b/cmd/baton-verkada/config.go @@ -13,6 +13,7 @@ type config struct { cli.BaseConfig `mapstructure:",squash"` // Puts the base config options in the same place as the connector options ApiKey string `mapstructure:"api-key"` + Region string `mapstructure:"region"` } // validateConfig is run after the configuration is loaded, and should return an error if it isn't valid. @@ -27,4 +28,5 @@ func validateConfig(ctx context.Context, cfg *config) error { // cmdFlags sets the cmdFlags required for the connector. func cmdFlags(cmd *cobra.Command) { cmd.PersistentFlags().String("api-key", "", "API key used to authenticate to Verkada API. ($BATON_API_KEY)") + cmd.PersistentFlags().String("region", "US", "API region. Default is US. In case of EU based organization, pass region as EU. ($BATON_REGION)") } diff --git a/cmd/baton-verkada/main.go b/cmd/baton-verkada/main.go index a4c590d..ad8ea34 100644 --- a/cmd/baton-verkada/main.go +++ b/cmd/baton-verkada/main.go @@ -39,7 +39,7 @@ func main() { func getConnector(ctx context.Context, cfg *config) (types.ConnectorServer, error) { l := ctxzap.Extract(ctx) - cb, err := connector.New(ctx, cfg.ApiKey) + cb, err := connector.New(ctx, cfg.ApiKey, cfg.Region) if err != nil { l.Error("error creating connector", zap.Error(err)) return nil, err diff --git a/pkg/connector/connector.go b/pkg/connector/connector.go index 0646f57..b01ff81 100644 --- a/pkg/connector/connector.go +++ b/pkg/connector/connector.go @@ -43,13 +43,13 @@ func (v *Connector) Validate(ctx context.Context) (annotations.Annotations, erro } // New returns a new instance of the connector. -func New(ctx context.Context, apiKey string) (*Connector, error) { +func New(ctx context.Context, apiKey, region string) (*Connector, error) { httpClient, err := uhttp.NewClient(ctx, uhttp.WithLogger(true, ctxzap.Extract(ctx))) if err != nil { return nil, err } return &Connector{ - client: verkada.NewClient(httpClient, apiKey), + client: verkada.NewClient(httpClient, apiKey, region), }, nil } diff --git a/pkg/connector/groups.go b/pkg/connector/groups.go index 97fad62..6571dbc 100644 --- a/pkg/connector/groups.go +++ b/pkg/connector/groups.go @@ -84,6 +84,7 @@ func (g *groupBuilder) Entitlements(_ context.Context, resource *v2.Resource, _ } func (g *groupBuilder) Grants(ctx context.Context, resource *v2.Resource, pToken *pagination.Token) ([]*v2.Grant, string, annotations.Annotations, error) { + l := ctxzap.Extract(ctx) users, err := g.client.ListUsers(ctx) if err != nil { return nil, "", nil, fmt.Errorf("error getting users: %w", err) @@ -94,7 +95,12 @@ func (g *groupBuilder) Grants(ctx context.Context, resource *v2.Resource, pToken userCopy := user accessInfo, err := g.client.GetUserAccessInformation(ctx, user.UserID) if err != nil { - return nil, "", nil, fmt.Errorf("error getting user %s access info: %w", user.UserID, err) + l.Warn( + "baton-verkada: error fetching user information, skipping user grant for group membership", + zap.String("user_id", user.UserID), + zap.String("group_id", resource.Id.Resource), + ) + continue } if groupContainsUser(resource.Id.Resource, accessInfo.AccessGroups) { diff --git a/pkg/verkada/client.go b/pkg/verkada/client.go index efaef24..90662d3 100644 --- a/pkg/verkada/client.go +++ b/pkg/verkada/client.go @@ -9,27 +9,34 @@ import ( "net/url" ) -const BaseUrl = "https://api.verkada.com" +const BaseUrlUS = "https://api.verkada.com" +const BaseUrlEU = "https://api.eu.verkada.com" type Client struct { httpClient *http.Client apiKey string + baseURL string } type RequestBody struct { UserID string `json:"user_id"` } -func NewClient(httpClient *http.Client, apiKey string) *Client { +func NewClient(httpClient *http.Client, apiKey, region string) *Client { + baseUrl := BaseUrlUS + if region != "US" { + baseUrl = BaseUrlEU + } return &Client{ httpClient: httpClient, apiKey: apiKey, + baseURL: baseUrl, } } // ListUsers returns a list of all access users. func (c *Client) ListUsers(ctx context.Context) ([]User, error) { - url, _ := url.JoinPath(BaseUrl, "/access/v1/access_users") + url, _ := url.JoinPath(c.baseURL, "/access/v1/access_users") var res struct { Users []User `json:"access_members"` @@ -44,7 +51,7 @@ func (c *Client) ListUsers(ctx context.Context) ([]User, error) { // GetUserAccessInformation returns user access information object. func (c *Client) GetUserAccessInformation(ctx context.Context, userId string) (UserAccess, error) { - accessUrl, _ := url.JoinPath(BaseUrl, "/access/v1/access_users/user") + accessUrl, _ := url.JoinPath(c.baseURL, "/access/v1/access_users/user") var res UserAccess q := url.Values{} @@ -59,7 +66,7 @@ func (c *Client) GetUserAccessInformation(ctx context.Context, userId string) (U // ListAccessGroups returns a list of all access groups. func (c *Client) ListAccessGroups(ctx context.Context) ([]Group, error) { - url, _ := url.JoinPath(BaseUrl, "/access/v1/access_groups") + url, _ := url.JoinPath(c.baseURL, "/access/v1/access_groups") var res struct { Groups []Group `json:"access_groups"` @@ -74,7 +81,7 @@ func (c *Client) ListAccessGroups(ctx context.Context) ([]Group, error) { // AddUserToGroup adds user to access group. func (c *Client) AddUserToGroup(ctx context.Context, groupId, userId string) error { - groupUrl, _ := url.JoinPath(BaseUrl, "/access/v1/access_groups/group/user") + groupUrl, _ := url.JoinPath(c.baseURL, "/access/v1/access_groups/group/user") var res struct { GroupID string `json:"group_id"` @@ -108,7 +115,7 @@ func (c *Client) AddUserToGroup(ctx context.Context, groupId, userId string) err // RemoveUserFromGroup removes user from access group. func (c *Client) RemoveUserFromGroup(ctx context.Context, groupId, userId string) error { - groupUrl, _ := url.JoinPath(BaseUrl, "/access/v1/access_groups/group/user") + groupUrl, _ := url.JoinPath(c.baseURL, "/access/v1/access_groups/group/user") q := url.Values{} q.Add("group_id", groupId) diff --git a/pkg/verkada/models.go b/pkg/verkada/models.go index 2a84122..cf737b5 100644 --- a/pkg/verkada/models.go +++ b/pkg/verkada/models.go @@ -21,5 +21,5 @@ type UserAccess struct { type Group struct { GroupID string `json:"group_id"` Name string `json:"name"` - UserIDS []string `json:"user_ids,omitempty"` + UserIDs []string `json:"user_ids,omitempty"` }