firezone

package module
v0.0.0-...-800f381 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 4, 2026 License: Apache-2.0 Imports: 16 Imported by: 0

README

Go Firezone

[!WARNING] This module has moved. It was published to the module proxy under the wrong name and all of its versions have been retracted. Development continues at firezone/firezone-sdk-go.

To migrate, change the import path - the package name stays firezone and no other code changes are needed:

go get github.com/firezone/firezone-sdk-go
import firezone "github.com/firezone/firezone-sdk-go"

Go Reference Go Report Card

A Go client for the Firezone REST API.

go get github.com/firezone/firezone-go
import firezone "github.com/firezone/firezone-go"

client, err := firezone.NewClient("https://api.firezone.dev", token)
if err != nil {
	// invalid base URL
}

site, err := client.Sites.Create(ctx, &firezone.CreateSiteRequest{Name: "primary-dc"})
if err != nil {
	if firezone.IsValidation(err) {
		// e.g. a Site with that name already exists - the API reports a
		// duplicate name as a 422 with a field-level error, not a 409
	}
	return err
}

gw, err := client.Sites.Gateways(site.ID).Provision(ctx, &firezone.ProvisionGatewayRequest{
	Name: "gw-nyc-1",
})
// gw.Token is only ever returned here, on Provision - the API never
// re-exposes it. See the ProvisionedGateway type's doc comment before
// storing it anywhere long-lived.

baseURL passed to NewClient is always the bare API host (https://api.firezone.dev). It must carry an http/https scheme and a host, and no query or fragment — NewClient rejects anything else rather than letting it surface later as a confusing transport error.

Resource IDs are validated and percent-escaped before they reach the URL, so an ID that arrived from config or upstream data can never redirect a call to a different endpoint. An empty ID fails with ErrMissingID before any request is sent:

_, err := client.Sites.Get(ctx, siteID)
if errors.Is(err, firezone.ErrMissingID) {
	// siteID was never populated
}

Versioning

This SDK is at 0.x: the exported API is not frozen, and a breaking change bumps the minor version. It is verified against a live Firezone portal and the surface is deliberate rather than provisional, but it has not yet had sustained real use. Pin a version in go.mod — as Go does by default — and read CHANGELOG.md before upgrading.

Requirements

Go 1.22 or newer. The SDK has no third-party dependencies — standard library only. CI builds against both the current Go release and the 1.22 floor, so the minimum is tested rather than assumed.

Resources

Client exposes one service per resource. These are read-write:

  • Sites
  • Resources
  • Policies
  • Groups
  • Actors
  • ClientDevices — Client devices. Named for ClientDevice, since Clients reads as the SDK's own client type.

These are read-only (list and get only):

  • EmailOTPAuthProviders, OIDCAuthProviders, GoogleAuthProviders, EntraAuthProviders, OktaAuthProviders
  • EntraDirectories, GoogleDirectories, OktaDirectories

Some API endpoints are deliberately not covered: /account, /logs, actor client tokens and external identities, the posture provider and managed device endpoints (Defender, Intune, IRU, Santa, SentinelOne), and /x509_auth_provider. Open an issue if you need one.

The Gateway token endpoints are a considered omission rather than a gap. A Gateway has at most one active token, and the whole of its life is covered: Gateways(siteID).Provision creates the Gateway and mints its token together, RotateToken replaces it, and Delete destroys the Gateway and revokes it. What is left out:

  • POST /sites/{site_id}/gateways/{gateway_id}/token — creates a token for a Gateway that has none. Every Gateway created through this SDK already has one, so this would always return 409; the API directs you to rotate instead. It is only useful for adopting a Gateway created in the admin portal.
  • POST /sites/{site_id}/gateway_tokens — a multi-owner token shared by all of a Site's Gateways, which the API marks deprecated.
  • The DELETE endpoints under /sites/{site_id}/gateway_tokens — deleting the Gateway revokes its token, which covers every case except a Gateway stranded past a rotation grace period. Recover from that by deleting and re-provisioning the Gateway.

Three services are nested under a parent, matching the API's own URL nesting:

  • client.Sites.Gateways(siteID)
  • client.Groups.Memberships(groupID)
  • client.Resources.PoolMembers(resourceID)

Every list method takes *ListOptions{Limit, PageCursor} and returns a *Page[T]{Data, Metadata}. See the resource file for each type's exact fields (sites.go, resources.go, policies.go, groups.go, memberships.go, actors.go, gateways.go, clients.go, auth_providers.go, directories.go, pool_members.go).

Updating nullable fields

The update endpoints are merge-patch: a field absent from the request body keeps its current value, and an explicit JSON null clears it. Fields the API allows to be null are typed *Null[T] so both are reachable:

_, err := client.Resources.Update(ctx, resourceID, &firezone.UpdateResourceRequest{
	Name:               "postgres-prod",              // set
	AddressDescription: firezone.Clear[string](),     // -> null, clears it
	SiteID:             firezone.Set(siteID),         // -> "site-..."
	// Address omitted entirely -> left untouched
})

Set("") clears a nullable string field too: the API treats an empty string as an empty value and replaces it with the field's default, which for a nullable field is null. Prefer Clear regardless — it states the intent, works for non-string types, and doesn't rely on that behavior.

The embedded lists (UpdatePolicyRequest.Conditions, UpdateResourceRequest.Filters) are *[]T for the same reason: nil leaves them alone, and a pointer to an empty slice removes all of them.

_, err := client.Policies.Update(ctx, policyID, &firezone.UpdatePolicyRequest{
	Conditions: &[]firezone.Condition{}, // remove every condition
})

Create*Request needs none of this — omitting an optional field on create already leaves it null.

A Client is safe for concurrent use by multiple goroutines; it holds no mutable state after construction.

Errors

Non-2xx responses are parsed into a typed *APIError (RFC 9457 problem+json). Use the Is* predicates rather than checking status codes directly:

switch {
case firezone.IsNotFound(err):
case firezone.IsConflict(err):
	// rare: the API reports most conflicts, including duplicate names,
	// as 422 validation errors rather than 409
case firezone.IsValidation(err):
	// err.(*firezone.APIError).ValidationErrors has field-level detail
case firezone.IsRateLimited(err):
case firezone.IsForbidden(err):
case firezone.IsUnauthorized(err):
}

Retries

Requests are retried automatically on HTTP 429 with exponential backoff, honoring the API's Retry-After header (10 attempts by default). Only 429 is retried — network errors and 5xx responses are returned to the caller, since neither is safe to assume idempotent. Disable or tune this via firezone.WithRetry:

client, _ := firezone.NewClient(endpoint, token, firezone.WithRetry(false, 0))

Timeouts

Each attempt is bounded by a 30 second timeout. That bounds one attempt, not a whole retried call — retry waits sit between requests rather than inside one — so a rate-limited call can still take longer overall, up to whatever budget WithRetry allows.

client, _ := firezone.NewClient(endpoint, token,
    firezone.WithRequestTimeout(10 * time.Second))

Pass 0 to impose no timeout of its own, leaving the deadline entirely to the caller.

The timeout is applied to the request context, not to the underlying http.Client, so it composes rather than competes. A Timeout on a client passed to WithHTTPClient, a deadline already on the context you pass in, and WithRequestTimeout all apply together — whichever expires first ends the attempt:

ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
site, err := client.Sites.Get(ctx, siteID)

Testing

mise run check             # everything CI runs, in one shot
mise run test              # unit tests, no server needed (httptest-based)
mise run test-floor        # build + test on the oldest supported Go
mise run spec-check        # struct tags vs the vendored OpenAPI spec
mise run vuln              # govulncheck
mise run test-acceptance   # requires FIREZONE_ENDPOINT/FIREZONE_TOKEN

The acceptance tests run against a real portal. A plain go test ./... never touches the network: they are behind a build tag and skip unless FIREZONE_ENDPOINT and FIREZONE_TOKEN are both set. mise run test-acceptance refuses to run at all without them, rather than skipping — Go reports an all-skipped package as ok, which is indistinguishable from a real pass. They are not part of CI — CI only type-checks them — so run them locally after changing anything that touches the wire. They create real objects, each named gosdk-<runID>-… and removed on cleanup.

To run them against a local dev server, boot the portal in a firezone/firezone checkout and mint a token:

# terminal 1 - the portal
cd /path/to/firezone/elixir && mix phx.server

# terminal 2 - mint a token and run the tests
cd /path/to/firezone/elixir
token=$(MIX_ENV=dev mix run --no-start script/seed_api_client_token.exs | tail -1)

cd /path/to/firezone-go
export FIREZONE_ENDPOINT=https://localhost:13001
export FIREZONE_TOKEN="$token"
export FIREZONE_CA_CERT=/path/to/firezone/elixir/priv/cert/selfsigned.pem
mise run test-acceptance

The dev API listens on HTTPS (port 13001 by default, overridable via PHOENIX_API_PORT) with a self-signed certificate, so FIREZONE_CA_CERT is needed unless that certificate is already in your machine's trust store. The seed script creates a fresh throwaway account and an api_client actor on every run and prints the bearer token as its last line; the token is valid for a day.

Because the account is fresh, the tests covering objects the API cannot create — Client devices and static device pools — will skip, and the auth provider and directory tests will report zero records. That is expected. See the terraform-provider-firezone README's "Local development" section for more on the dev environment.

Contributing

See CONTRIBUTING.md. To report a security issue, see SECURITY.md.

License

Licensed under the Apache License, Version 2.0. See LICENSE and NOTICE for details.

Documentation

Overview

Package firezone provides a hand-written Go client for the Firezone REST API (https://www.firezone.dev).

Construct a Client with NewClient, then call methods on its resource services (Sites, Resources, Policies, Groups, Actors, and Gateways nested under Sites):

client, err := firezone.NewClient("https://api.firezone.dev", token)
site, err := client.Sites.Create(ctx, &firezone.CreateSiteRequest{Name: "primary-dc"})

The API is currently unversioned - baseURL is the bare API host, with no path prefix of any kind. (URL path versioning was tried and rolled back before shipping; if it returns, it'll live in exactly one place here rather than every call site.)

A Client is safe for concurrent use by multiple goroutines.

Update requests are merge-patch: a field left at its zero value is omitted and keeps its current value on the server. Fields the API allows to be null are typed Null so they can be cleared as well as set - see Clear and Set.

Index

Examples

Constants

View Source
const Version = "0.1.0"

Version is this SDK's released version, following semantic versioning. It is sent as part of the default User-Agent, so a Firezone operator can tell which client version a request came from.

It is a constant rather than something read from build info, because build info reports "(devel)" whenever the module is built rather than consumed. Bump it as part of cutting a release - see CONTRIBUTING.md.

Variables

View Source
var ErrMissingID = errors.New("must not be empty")

ErrMissingID is returned when a method is called with an empty or otherwise unusable resource ID. Test for it with errors.Is:

if errors.Is(err, firezone.ErrMissingID) { ... }

It is returned before any request is made, so an ID that came from unpopulated config fails loudly rather than being sent as an empty path segment - which would silently address the collection endpoint instead (GET /sites rather than GET /sites/{id}).

View Source
var ErrNilRequest = errors.New("request must not be nil")

ErrNilRequest is returned when a Create or Update method is called with a nil request body. Test for it with errors.Is:

if errors.Is(err, firezone.ErrNilRequest) { ... }

It is returned before any request is made. Without it a nil request encodes as {"site": null} rather than being caught: v is an any holding a typed nil pointer, so a plain v == nil check is false and json.Marshal happily writes the null.

Functions

func IsConflict

func IsConflict(err error) bool

IsConflict reports whether err is an *APIError with StatusCode 409.

The API returns 409 from a single endpoint - creating a token for a Gateway that already has one - which this SDK does not wrap, for the reasons in the GatewaysService doc comment. Most things that read like conflicts, a duplicate name among them, come back as 422 validation errors instead, so reach for IsValidation first.

func IsForbidden

func IsForbidden(err error) bool

IsForbidden reports whether err is an *APIError with StatusCode 403.

func IsNotFound

func IsNotFound(err error) bool

IsNotFound reports whether err is an *APIError with StatusCode 404.

func IsRateLimited

func IsRateLimited(err error) bool

IsRateLimited reports whether err is an *APIError with StatusCode 429.

func IsUnauthorized

func IsUnauthorized(err error) bool

IsUnauthorized reports whether err is an *APIError with StatusCode 401.

func IsValidation

func IsValidation(err error) bool

IsValidation reports whether err is an *APIError with StatusCode 422.

Example

The API reports a duplicate name as a validation error rather than a conflict, with the offending field named in ValidationErrors.

package main

import (
	"context"
	"errors"
	"fmt"

	firezone "github.com/firezone/firezone-go"
)

func main() {
	var client *firezone.Client // see [NewClient]

	_, err := client.Sites.Create(context.Background(), &firezone.CreateSiteRequest{Name: "primary-dc"})
	if firezone.IsValidation(err) {
		var apiErr *firezone.APIError
		if errors.As(err, &apiErr) {
			for field, messages := range apiErr.ValidationErrors {
				fmt.Printf("%s: %v\n", field, messages)
			}
		}
	}
}

func String

func String(s string) *string

String returns a pointer to s. Useful for optional string fields (e.g. GroupListOptions.DirectoryID) where a plain string's zero value can't distinguish "not set" from "set to the empty string".

Types

type APIError

type APIError struct {
	// StatusCode is the HTTP status code of the response.
	StatusCode int
	// Type is the RFC 9457 problem type URI. The API always returns
	// "about:blank".
	Type string
	// Title is a short, human-readable summary of the problem, derived
	// from the HTTP status code (e.g. "Not Found").
	Title string
	// Detail is a human-readable explanation specific to this occurrence
	// of the problem.
	Detail string
	// ValidationErrors maps field names to validation failure messages.
	// Populated only when StatusCode is 422.
	ValidationErrors map[string][]string
	// RetryAfter is how long to wait before retrying, parsed from the
	// Retry-After response header. Populated only when StatusCode is 429.
	RetryAfter time.Duration
}

APIError represents an RFC 9457 (Problem Details for HTTP APIs) error response from the Firezone API.

func (*APIError) Error

func (e *APIError) Error() string

Error implements the error interface.

type Actor

type Actor struct {
	ID                   string     `json:"id"`
	Name                 string     `json:"name"`
	Type                 ActorType  `json:"type"`
	Email                string     `json:"email,omitempty"`
	AllowEmailOTPSignIn  bool       `json:"allow_email_otp_sign_in"`
	IsDisabled           bool       `json:"is_disabled"`
	LastSeenAt           *time.Time `json:"last_seen_at,omitempty"`
	CreatedByDirectoryID string     `json:"created_by_directory_id,omitempty"`
	InsertedAt           time.Time  `json:"inserted_at"`
	UpdatedAt            time.Time  `json:"updated_at"`
}

Actor is a Firezone Actor - a user, admin, or service account.

func (*Actor) IsSynced

func (a *Actor) IsSynced() bool

IsSynced reports whether the Actor was created by an identity provider directory sync, and so is owned by the IdP rather than by this API. It mirrors Group.IsSynced.

Unlike a synced Group, a synced Actor is not wholly read-only here - but its existence and identity are the directory's to decide, so tooling that adopts an existing account should treat it as discovered rather than managed.

type ActorListOptions

type ActorListOptions struct {
	ListOptions
	// Name filters to Actors with this exact name.
	Name string
	// Email filters to Actors with this exact email.
	Email string
	// Type filters to Actors of this type. Unlike [CreateActorRequest]
	// and [UpdateActorRequest], "api_client" is a valid filter value
	// here - it just can't be created or updated via the API.
	Type ActorType
}

ActorListOptions extends ListOptions with Actors-specific filters.

type ActorType

type ActorType string

ActorType is the type of a Firezone Actor. api_client is intentionally not offered as a constant here: it cannot be created, updated, or otherwise managed via this API (the API returns 422 if you try), since api_client actors are how API tokens themselves are issued.

const (
	ActorTypeAccountUser      ActorType = "account_user"
	ActorTypeAccountAdminUser ActorType = "account_admin_user"
	ActorTypeServiceAccount   ActorType = "service_account"
)

ActorType values.

type ActorsService

type ActorsService struct {
	// contains filtered or unexported fields
}

ActorsService manages Actors.

func (*ActorsService) Create

func (s *ActorsService) Create(ctx context.Context, req *CreateActorRequest) (*Actor, error)

Create creates a new Actor.

func (*ActorsService) Delete

func (s *ActorsService) Delete(ctx context.Context, id string) error

Delete deletes an Actor.

func (*ActorsService) Disable

func (s *ActorsService) Disable(ctx context.Context, id string) (*Actor, error)

Disable disables an Actor, immediately revoking all of its active Client tokens and portal sessions. Returns 403 Forbidden if id is the authenticated actor itself.

This is a convenience wrapper over ActorsService.Update; the API has no dedicated disable endpoint.

func (*ActorsService) Enable

func (s *ActorsService) Enable(ctx context.Context, id string) (*Actor, error)

Enable enables a disabled Actor. Idempotent - enabling an already-enabled Actor is a no-op.

This is a convenience wrapper over ActorsService.Update; the API has no dedicated enable endpoint.

func (*ActorsService) Get

func (s *ActorsService) Get(ctx context.Context, id string) (*Actor, error)

Get fetches a single Actor by ID.

func (*ActorsService) List

func (s *ActorsService) List(ctx context.Context, opts *ActorListOptions) (*Page[Actor], error)

List returns a page of Actors. Pass nil for opts to use the API's default page size and no filters.

func (*ActorsService) Update

func (s *ActorsService) Update(ctx context.Context, id string, req *UpdateActorRequest) (*Actor, error)

Update updates an Actor.

type AuthProvider

type AuthProvider struct {
	ID        string `json:"id"`
	AccountID string `json:"account_id"`
	Name      string `json:"name"`
	Issuer    string `json:"issuer"`
	Context   string `json:"context"`

	// ClientSessionLifetimeSecs and PortalSessionLifetimeSecs are how
	// long a sign-in through this provider stays valid on a Client
	// device and in the admin portal respectively.
	//
	// Both are nil when the provider sets no override and Firezone's own
	// defaults apply, which is the usual case - the columns have no
	// database default, so a provider that has never had them configured
	// stores null. They are pointers for that reason: a plain int would
	// decode null to 0, which reads as "sessions expire immediately"
	// rather than "not configured".
	//
	// The spec marks these nullable only on the Entra provider, but the
	// underlying schema is identical for all five, so treat every one of
	// them as nullable.
	ClientSessionLifetimeSecs *int `json:"client_session_lifetime_secs,omitempty"`
	PortalSessionLifetimeSecs *int `json:"portal_session_lifetime_secs,omitempty"`

	IsDisabled bool `json:"is_disabled"`

	InsertedAt time.Time `json:"inserted_at"`
	UpdatedAt  time.Time `json:"updated_at"`
}

AuthProvider is the state every authentication provider type shares. Each concrete type embeds it and adds its own fields.

Auth providers are read-only through this API: they are configured in the Firezone dashboard, which owns the OAuth/OIDC secrets involved. The Policy condition property "auth_provider_id" takes these IDs.

type AuthProviderListOptions

type AuthProviderListOptions struct {
	ListOptions
	// Name filters to providers with this exact name, as shown in the
	// dashboard's authentication settings.
	Name string
}

AuthProviderListOptions extends ListOptions with the filters every auth provider list endpoint accepts. Shared across the five types, which expose the same filter surface.

type Client

type Client struct {

	// Sites manages Sites and, nested under them, Gateways.
	Sites *SitesService
	// Resources manages Resources.
	Resources *ResourcesService
	// Policies manages Policies.
	Policies *PoliciesService
	// Groups manages Groups and, nested under them, memberships.
	Groups *GroupsService
	// Actors manages Actors.
	Actors *ActorsService
	// ClientDevices manages Client devices. Named for [ClientDevice],
	// since Clients is too easily confused with this type itself.
	ClientDevices *ClientsService
	// EmailOTPAuthProviders reads Email OTP auth providers (read-only).
	EmailOTPAuthProviders *EmailOTPAuthProvidersService
	// OIDCAuthProviders reads generic OIDC auth providers (read-only).
	OIDCAuthProviders *OIDCAuthProvidersService
	// GoogleAuthProviders reads Google Workspace auth providers
	// (read-only).
	GoogleAuthProviders *GoogleAuthProvidersService
	// EntraAuthProviders reads Microsoft Entra auth providers
	// (read-only).
	EntraAuthProviders *EntraAuthProvidersService
	// OktaAuthProviders reads Okta auth providers (read-only).
	OktaAuthProviders *OktaAuthProvidersService
	// EntraDirectories reads Microsoft Entra directory connections
	// (read-only).
	EntraDirectories *EntraDirectoriesService
	// GoogleDirectories reads Google Workspace directory connections
	// (read-only).
	GoogleDirectories *GoogleDirectoriesService
	// OktaDirectories reads Okta directory connections (read-only).
	OktaDirectories *OktaDirectoriesService
	// contains filtered or unexported fields
}

Client is a Firezone REST API client.

A Client is safe for concurrent use by multiple goroutines: it holds no mutable state once NewClient returns, and each request builds its own URL and body.

func NewClient

func NewClient(baseURL, token string, opts ...Option) (*Client, error)

NewClient constructs a Firezone API client. baseURL is the bare API host (e.g. "https://api.firezone.dev") - do not include a version segment. token is the Bearer token for an api_client actor.

Requests go through an *http.Client private to this SDK, and each attempt is bounded by defaultRequestTimeout. Pass WithHTTPClient to supply your own client and WithRequestTimeout to change the bound.

Example
package main

import (
	"context"
	"fmt"
	"log"
	"os"

	firezone "github.com/firezone/firezone-go"
)

func main() {
	client, err := firezone.NewClient("https://api.firezone.dev", os.Getenv("FIREZONE_TOKEN"))
	if err != nil {
		// Only a malformed base URL gets here - it must carry an
		// http/https scheme and a host, and no query or fragment.
		log.Fatal(err)
	}

	site, err := client.Sites.Create(context.Background(), &firezone.CreateSiteRequest{
		Name: "primary-dc",
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(site.Name)
}

type ClientDevice

type ClientDevice struct {
	ID           string `json:"id"`
	FirezoneID   string `json:"firezone_id"`
	ActorID      string `json:"actor_id"`
	Name         string `json:"name"`
	IPv4         string `json:"ipv4"`
	IPv6         string `json:"ipv6"`
	Online       bool   `json:"online"`
	PublicKey    string `json:"public_key,omitempty"`
	Hostname     string `json:"hostname,omitempty"`
	DeviceSerial string `json:"device_serial,omitempty"`
	DeviceUUID   string `json:"device_uuid,omitempty"`

	// VerifiedAt is nil until an admin verifies the device. Policies can
	// require verification via the client_verified condition property.
	VerifiedAt *time.Time `json:"verified_at,omitempty"`

	// LastSeenAt is nil for a Client that has enrolled but never
	// connected.
	LastSeenAt       *time.Time `json:"last_seen_at,omitempty"`
	LastSeenVersion  string     `json:"last_seen_version,omitempty"`
	LastSeenRemoteIP string     `json:"last_seen_remote_ip,omitempty"`

	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

ClientDevice is a Firezone Client - an enrolled end-user device.

The API calls this a "Client", but Client is already this SDK's own API client type, so the device concept carries the Device suffix here.

Client devices are not created through this API: a device registers itself when it first connects. They can be read, renamed, verified, and deleted, but never provisioned - so there is no CreateClientRequest.

type ClientListOptions

type ClientListOptions struct {
	ListOptions
	// Name filters to Clients with this exact name. Names are not
	// unique, so this can still match more than one.
	Name string
	// FirezoneID filters to Clients with this exact Firezone ID. Unique
	// per actor rather than per account, so this too can match more than
	// one - though in practice rarely does.
	FirezoneID string
}

ClientListOptions extends ListOptions with Clients-specific filters.

type ClientsService

type ClientsService struct {
	// contains filtered or unexported fields
}

ClientsService manages Clients.

func (*ClientsService) Delete

func (s *ClientsService) Delete(ctx context.Context, id string) error

Delete deletes a Client, unenrolling the device.

func (*ClientsService) Get

func (s *ClientsService) Get(ctx context.Context, id string) (*ClientDevice, error)

Get fetches a single Client by ID.

func (*ClientsService) List

List returns a page of Clients. Pass nil for opts to use the API's default page size and no filters.

func (*ClientsService) Unverify

func (s *ClientsService) Unverify(ctx context.Context, id string) (*ClientDevice, error)

Unverify clears a Client's verification.

func (*ClientsService) Update

Update renames a Client.

func (*ClientsService) Verify

func (s *ClientsService) Verify(ctx context.Context, id string) (*ClientDevice, error)

Verify marks a Client as admin-verified, satisfying the client_verified Policy condition.

type Condition

type Condition struct {
	Property ConditionProperty `json:"property"`
	Operator ConditionOperator `json:"operator"`
	Values   []string          `json:"values"`
}

Condition restricts when a Policy grants access. All Conditions on a Policy must evaluate to true for access to be granted.

Which Operators are valid, and how Values is interpreted, depends on Property:

type ConditionOperator

type ConditionOperator string

ConditionOperator is the comparison a policy Condition applies between the subject property and Values. Which operators are valid depends on Property - see the API's policy schema documentation.

const (
	ConditionOperatorIsIn        ConditionOperator = "is_in"
	ConditionOperatorIsNotIn     ConditionOperator = "is_not_in"
	ConditionOperatorIsInCIDR    ConditionOperator = "is_in_cidr"
	ConditionOperatorIsNotInCIDR ConditionOperator = "is_not_in_cidr"

	// ConditionOperatorIsInDayOfWeekTimeRanges matches when the current
	// time falls inside one of the given weekly windows. Each value is a
	// "DAY/TIME_RANGES/TIMEZONE" string, where DAY is one of M T W R F S
	// U (Monday through Sunday), TIME_RANGES is a comma-separated list of
	// HH:MM-HH:MM ranges, and TIMEZONE is an IANA timezone name - for
	// example "M/09:00-17:00/America/New_York".
	//
	// All three segments are required; the API rejects a value with no
	// timezone. Days are specified one value per day, so a Monday-Friday
	// window is five values, not one.
	ConditionOperatorIsInDayOfWeekTimeRanges ConditionOperator = "is_in_day_of_week_time_ranges"

	ConditionOperatorIs ConditionOperator = "is"
)

ConditionOperator values.

type ConditionProperty

type ConditionProperty string

ConditionProperty is the subject property a policy Condition evaluates.

const (
	ConditionPropertyRemoteIPLocationRegion ConditionProperty = "remote_ip_location_region"
	ConditionPropertyRemoteIP               ConditionProperty = "remote_ip"
	ConditionPropertyAuthProviderID         ConditionProperty = "auth_provider_id"
	ConditionPropertyCurrentUTCDatetime     ConditionProperty = "current_utc_datetime"
	ConditionPropertyClientVerified         ConditionProperty = "client_verified"
)

ConditionProperty values.

type CreateActorRequest

type CreateActorRequest struct {
	Name                string    `json:"name"`
	Type                ActorType `json:"type"`
	Email               string    `json:"email,omitempty"`
	AllowEmailOTPSignIn *bool     `json:"allow_email_otp_sign_in,omitempty"`
}

CreateActorRequest is the request body for ActorsService.Create.

type CreateGroupRequest

type CreateGroupRequest struct {
	Name string `json:"name"`
}

CreateGroupRequest is the request body for GroupsService.Create.

type CreatePolicyRequest

type CreatePolicyRequest struct {
	GroupID               string      `json:"group_id"`
	ResourceID            string      `json:"resource_id"`
	Description           string      `json:"description,omitempty"`
	FlowLogUploadsEnabled *bool       `json:"flow_log_uploads_enabled,omitempty"`
	Conditions            []Condition `json:"conditions,omitempty"`
}

CreatePolicyRequest is the request body for PoliciesService.Create.

type CreateResourceRequest

type CreateResourceRequest struct {
	Name               string       `json:"name"`
	Type               ResourceType `json:"type"`
	Address            string       `json:"address,omitempty"`
	AddressDescription string       `json:"address_description,omitempty"`
	IPStack            IPStack      `json:"ip_stack,omitempty"`
	SiteID             string       `json:"site_id,omitempty"`
	Filters            []Filter     `json:"filters,omitempty"`
}

CreateResourceRequest is the request body for ResourcesService.Create.

type CreateSiteRequest

type CreateSiteRequest struct {
	Name string `json:"name"`
}

CreateSiteRequest is the request body for SitesService.Create.

type DirectoryListOptions

type DirectoryListOptions struct {
	ListOptions
	// Name filters to directories with this exact name, as shown in the
	// dashboard's identity provider settings.
	Name string
}

DirectoryListOptions extends ListOptions with the filters every directory list endpoint accepts. Shared across the three providers, which expose the same filter surface.

type EmailOTPAuthProvider

type EmailOTPAuthProvider struct {
	AuthProvider
}

EmailOTPAuthProvider signs users in with a one-time passcode emailed to them. Unlike the other types it has no is_default flag - it is a fallback rather than something a sign-in page defaults to.

type EmailOTPAuthProvidersService

type EmailOTPAuthProvidersService struct {
	// contains filtered or unexported fields
}

EmailOTPAuthProvidersService reads Email OTP auth providers. Read-only - see AuthProvider.

func (*EmailOTPAuthProvidersService) Get

Get fetches a single Email OTP auth provider by ID.

func (*EmailOTPAuthProvidersService) List

List returns a page of Email OTP auth providers. Pass nil for opts to use the API's default page size and no filters.

type EntraAuthProvider

type EntraAuthProvider struct {
	AuthProvider
	IsDefault  bool   `json:"is_default"`
	EmailClaim string `json:"email_claim"`
}

EntraAuthProvider is a Microsoft Entra sign-in provider.

type EntraAuthProvidersService

type EntraAuthProvidersService struct {
	// contains filtered or unexported fields
}

EntraAuthProvidersService reads Microsoft Entra auth providers. Read-only - see AuthProvider.

func (*EntraAuthProvidersService) Get

Get fetches a single Entra auth provider by ID.

func (*EntraAuthProvidersService) List

List returns a page of Entra auth providers. Pass nil for opts to use the API's default page size and no filters.

type EntraDirectoriesService

type EntraDirectoriesService struct {
	// contains filtered or unexported fields
}

EntraDirectoriesService reads Entra directory connections. Read-only: there is no Create, Update, or Delete - see EntraDirectory.

func (*EntraDirectoriesService) Get

Get fetches a single Entra directory by ID.

func (*EntraDirectoriesService) List

List returns a page of Entra directories. Pass nil for opts to use the API's default page size and no filters.

type EntraDirectory

type EntraDirectory struct {
	ID             string     `json:"id"`
	AccountID      string     `json:"account_id"`
	Name           string     `json:"name"`
	TenantID       string     `json:"tenant_id"`
	IsDisabled     bool       `json:"is_disabled"`
	DisabledReason string     `json:"disabled_reason,omitempty"`
	SyncedAt       *time.Time `json:"synced_at,omitempty"`
	ErrorMessage   string     `json:"error_message,omitempty"`
	ErroredAt      *time.Time `json:"errored_at,omitempty"`
	EmailField     string     `json:"email_field"`
	SyncAllGroups  bool       `json:"sync_all_groups"`
	InsertedAt     time.Time  `json:"inserted_at"`
	UpdatedAt      time.Time  `json:"updated_at"`
}

EntraDirectory is a Microsoft Entra directory connection. Directories are read-only via this API - they're managed through the Firezone dashboard's identity provider setup, not created or updated here.

type Filter

type Filter struct {
	Protocol FilterProtocol `json:"protocol"`
	// Ports are port numbers or ranges (e.g. "80" or "8000 - 9000").
	// Not applicable to FilterProtocolICMP.
	Ports []string `json:"ports,omitempty"`
}

Filter restricts the protocols and ports a Resource exposes.

type FilterProtocol

type FilterProtocol string

FilterProtocol is the transport protocol a Filter applies to.

const (
	FilterProtocolTCP  FilterProtocol = "tcp"
	FilterProtocolUDP  FilterProtocol = "udp"
	FilterProtocolICMP FilterProtocol = "icmp"
)

FilterProtocol values.

type Gateway

type Gateway struct {
	ID     string `json:"id"`
	Name   string `json:"name"`
	IPv4   string `json:"ipv4"`
	IPv6   string `json:"ipv6"`
	Online bool   `json:"online"`

	// GatewayTokenID is the token this Gateway last connected with.
	// Empty until the Gateway connects for the first time.
	GatewayTokenID string `json:"gateway_token_id,omitempty"`

	// RotatedAt is when the token named by GatewayTokenID was rotated
	// out, and is nil in the normal case. A non-nil value means a
	// replacement token has been minted and this Gateway has not picked
	// it up yet - see [Gateway.RotationPending].
	RotatedAt *time.Time `json:"rotated_at,omitempty"`
}

Gateway is a Firezone Gateway - a host that exposes a Site's Resources to Clients.

IPv4 and IPv6 are the Gateway's tunnel addresses, allocated when the Gateway is created rather than on first connect, so both are always populated - including on a Gateway that has never connected. See LastSeenRemoteIP on the API's own responses for the public address.

func (*Gateway) RotationPending

func (g *Gateway) RotationPending() bool

RotationPending reports whether a replacement token has been minted that this Gateway has not yet connected with.

While it is true the current token stays valid only until the Gateway connects with the replacement or the API's rotation grace period elapses from Gateway.RotatedAt, whichever comes first. A Gateway left in this state past the grace period is stranded: the API deletes the previous token once the replacement is confirmed, so rolling a host's configuration back to it will not work.

type GatewayListOptions

type GatewayListOptions struct {
	ListOptions
	// Name filters to the Gateway with this exact name.
	Name string
	// IPv4 filters to the Gateway with this exact IPv4 address.
	IPv4 string
	// IPv6 filters to the Gateway with this exact IPv6 address.
	IPv6 string
}

GatewayListOptions extends ListOptions with Gateways-specific filters. There's no SiteID filter here - the Site is already fixed by which GatewaysService you called List on (see SitesService.Gateways).

type GatewaysService

type GatewaysService struct {
	// contains filtered or unexported fields
}

GatewaysService manages the Gateways belonging to a single Site. Obtain one via SitesService.Gateways.

Token lifecycle

A Gateway has at most one active token, and this service covers the whole of that token's life: GatewaysService.Provision creates the Gateway and mints its token together, GatewaysService.RotateToken replaces it, and GatewaysService.Delete destroys the Gateway and revokes it.

Two API endpoints are deliberately left out of that set:

  • POST /sites/{site_id}/gateways/{gateway_id}/token creates a token for a Gateway that has none. Every Gateway this SDK creates goes through Provision, which already mints one, so calling it would always fail with 409 Conflict - the API allows only one active token per Gateway and directs callers to rotate instead. The endpoint is only useful for a Gateway created elsewhere, such as in the admin portal.
  • POST /sites/{site_id}/gateway_tokens creates a multi-owner token shared by all of a Site's Gateways. The API marks it deprecated in favour of the per-Gateway endpoint above.

The DELETE counterparts under /sites/{site_id}/gateway_tokens are absent for the same reason: a token this SDK can create belongs to a Gateway, and deleting that Gateway revokes it. The one case this leaves uncovered is a Gateway stranded past a rotation grace period (see Gateway.RotationPending), which is recovered by deleting and re-provisioning the Gateway rather than by deleting its token.

If you need to adopt Gateways created outside this SDK, the token endpoints above are the gap to fill - adding them is additive, and IsConflict is already here for the 409 the first one returns.

func (*GatewaysService) Delete

func (s *GatewaysService) Delete(ctx context.Context, id string) error

Delete deletes a Gateway, revoking its token. This is the only way this SDK revokes a token: see the GatewaysService doc comment for why the API's standalone token-deletion endpoints are not wrapped.

func (*GatewaysService) Get

func (s *GatewaysService) Get(ctx context.Context, id string) (*Gateway, error)

Get fetches a single Gateway by ID.

func (*GatewaysService) List

List returns a page of the Site's Gateways. Pass nil for opts to use the API's default page size and no filters.

func (*GatewaysService) Provision

Provision creates a new Gateway and mints its single-owner token in one call. The returned Token is shown once - store it securely.

func (*GatewaysService) RotateToken

func (s *GatewaysService) RotateToken(ctx context.Context, id string) (*RotatedGatewayToken, error)

RotateToken mints a replacement single-owner token for the Gateway, returning the new secret once.

The Gateway's current token is not invalidated immediately: it keeps working until the Gateway first connects with the replacement or the API's grace period elapses, whichever comes first. That window is the point - it exists so the replacement can be delivered to the Gateway host without downtime.

Two consequences worth planning for:

  • Deliver the replacement and restart the Gateway before the grace period expires, or the Gateway is stranded. Poll Gateway.RotationPending to confirm pickup.
  • Once pickup is confirmed the previous token is deleted, so rolling a host's configuration back to it will not work.

Rotating again before the Gateway picks up a pending replacement replaces only that pending token; the in-use one keeps its original deadline.

func (*GatewaysService) Update

Update renames a Gateway.

type GoogleAuthProvider

type GoogleAuthProvider struct {
	AuthProvider
	IsDefault bool `json:"is_default"`
}

GoogleAuthProvider is a Google Workspace sign-in provider.

type GoogleAuthProvidersService

type GoogleAuthProvidersService struct {
	// contains filtered or unexported fields
}

GoogleAuthProvidersService reads Google Workspace auth providers. Read-only - see AuthProvider.

func (*GoogleAuthProvidersService) Get

Get fetches a single Google auth provider by ID.

func (*GoogleAuthProvidersService) List

List returns a page of Google auth providers. Pass nil for opts to use the API's default page size and no filters.

type GoogleDirectoriesService

type GoogleDirectoriesService struct {
	// contains filtered or unexported fields
}

GoogleDirectoriesService reads Google Workspace directory connections. Read-only: there is no Create, Update, or Delete - see GoogleDirectory.

func (*GoogleDirectoriesService) Get

Get fetches a single Google Workspace directory by ID.

func (*GoogleDirectoriesService) List

List returns a page of Google Workspace directories. Pass nil for opts to use the API's default page size.

type GoogleDirectory

type GoogleDirectory struct {
	ID                 string     `json:"id"`
	AccountID          string     `json:"account_id"`
	Name               string     `json:"name"`
	Domain             string     `json:"domain"`
	ImpersonationEmail string     `json:"impersonation_email"`
	IsDisabled         bool       `json:"is_disabled"`
	DisabledReason     string     `json:"disabled_reason,omitempty"`
	SyncedAt           *time.Time `json:"synced_at,omitempty"`
	ErrorMessage       string     `json:"error_message,omitempty"`
	ErroredAt          *time.Time `json:"errored_at,omitempty"`
	GroupSyncMode      string     `json:"group_sync_mode"`
	OrgUnitSyncEnabled bool       `json:"orgunit_sync_enabled"`
	InsertedAt         time.Time  `json:"inserted_at"`
	UpdatedAt          time.Time  `json:"updated_at"`
}

GoogleDirectory is a Google Workspace directory connection. Directories are read-only via this API - they're managed through the Firezone dashboard's identity provider setup, not created or updated here.

type Group

type Group struct {
	ID          string     `json:"id"`
	Name        string     `json:"name"`
	Email       string     `json:"email,omitempty"`
	EntityType  string     `json:"entity_type,omitempty"`
	DirectoryID string     `json:"directory_id,omitempty"`
	IdpID       string     `json:"idp_id,omitempty"`
	SyncedAt    *time.Time `json:"synced_at,omitempty"`
	InsertedAt  time.Time  `json:"inserted_at"`
	UpdatedAt   time.Time  `json:"updated_at"`
}

Group is a Firezone actor Group. Groups synced from an identity provider have a non-empty DirectoryID and are read-only via this API (writes return 403 Forbidden) - see Group.IsSynced.

func (*Group) IsSynced

func (g *Group) IsSynced() bool

IsSynced reports whether the Group is managed by an identity provider sync, and therefore read-only via this API.

type GroupListOptions

type GroupListOptions struct {
	ListOptions
	// Name filters to Groups with this exact name.
	Name string
	// DirectoryID filters to Groups synced from this directory. A
	// pointer since the two meaningful "set" states aren't
	// distinguishable through a plain string's zero value: nil means no
	// filter, a pointer to "" filters to unsynced (native) Groups only,
	// and a pointer to a directory ID filters to that directory. Use
	// [String] to build the pointer inline, e.g. DirectoryID:
	// firezone.String("").
	DirectoryID *string
	// EntityType filters to Groups of this entity type ("group" or
	// "org_unit").
	EntityType string
}

GroupListOptions extends ListOptions with Groups-specific filters.

type GroupMember

type GroupMember struct {
	ID   string    `json:"id"`
	Name string    `json:"name"`
	Type ActorType `json:"type"`
}

GroupMember is an Actor's minimal representation as returned by MembershipsService.List.

type GroupsService

type GroupsService struct {
	// contains filtered or unexported fields
}

GroupsService manages Groups, and, nested under them, memberships.

func (*GroupsService) Create

func (s *GroupsService) Create(ctx context.Context, req *CreateGroupRequest) (*Group, error)

Create creates a new (unsynced) Group.

func (*GroupsService) Delete

func (s *GroupsService) Delete(ctx context.Context, id string) error

Delete deletes a Group. Returns 403 Forbidden if the Group is synced from an identity provider (see Group.IsSynced).

func (*GroupsService) Get

func (s *GroupsService) Get(ctx context.Context, id string) (*Group, error)

Get fetches a single Group by ID.

func (*GroupsService) List

func (s *GroupsService) List(ctx context.Context, opts *GroupListOptions) (*Page[Group], error)

List returns a page of Groups. Pass nil for opts to use the API's default page size and no filters.

func (*GroupsService) Memberships

func (s *GroupsService) Memberships(groupID string) *MembershipsService

Memberships returns a MembershipsService scoped to the Group identified by groupID.

func (*GroupsService) Update

func (s *GroupsService) Update(ctx context.Context, id string, req *UpdateGroupRequest) (*Group, error)

Update updates a Group. Returns 403 Forbidden if the Group is synced from an identity provider (see Group.IsSynced).

type IPStack

type IPStack string

IPStack constrains which IP families a Resource is reachable over.

const (
	IPStackIPv4Only IPStack = "ipv4_only"
	IPStackIPv6Only IPStack = "ipv6_only"
	IPStackDual     IPStack = "dual"
)

IPStack values.

type ListOptions

type ListOptions struct {
	// Limit is the maximum number of items to return. The API clamps
	// this to the range [1, 100]; zero means "use the API default" (50).
	Limit int
	// PageCursor requests the page following (or preceding, when used
	// with a PrevPage cursor) the one it was returned from. Leave empty
	// to request the first page.
	PageCursor string
}

ListOptions controls pagination for List methods. The zero value requests the API's default page (limit 50).

Example

Paging is an explicit cursor loop: keep requesting until the metadata stops handing back a next-page cursor.

package main

import (
	"context"
	"fmt"
	"log"

	firezone "github.com/firezone/firezone-go"
)

func main() {
	var client *firezone.Client // see [NewClient]

	opts := &firezone.ResourceListOptions{
		ListOptions: firezone.ListOptions{Limit: 100},
	}
	for {
		page, err := client.Resources.List(context.Background(), opts)
		if err != nil {
			log.Fatal(err)
		}
		for _, resource := range page.Data {
			fmt.Println(resource.Name)
		}
		if page.Metadata.NextPage == "" {
			break
		}
		opts.PageCursor = page.Metadata.NextPage
	}
}

type MembershipsService

type MembershipsService struct {
	// contains filtered or unexported fields
}

MembershipsService manages a single Group's memberships. Obtain one via GroupsService.Memberships.

func (*MembershipsService) List

List returns a page of the Group's members.

func (*MembershipsService) Patch

func (s *MembershipsService) Patch(ctx context.Context, add, remove []string) ([]string, error)

Patch adds and removes members from the Group without disturbing any other membership, returning the resulting member actor IDs. This is the safe choice when more than one caller manages membership on the same Group independently.

The operation is idempotent - adding an actor already in the Group and removing one that isn't are both no-ops - so a request that may already have been applied is safe to retry. Removals are applied before additions, so an ID passed in both add and remove ends up a member. Repeating an ID within either list is not an error; both are deduplicated. The returned IDs are sorted.

func (*MembershipsService) ReplaceAll

func (s *MembershipsService) ReplaceAll(ctx context.Context, actorIDs []string) ([]string, error)

ReplaceAll replaces the Group's entire membership list with actorIDs, returning the resulting member actor IDs. Unlike [Patch], this is a full replace: any actor not in actorIDs is removed.

Prefer [Patch] when multiple independent callers manage membership on the same Group - ReplaceAll from more than one caller will overwrite each other's changes.

Members that are not changing keep their server-side membership rows, so replacing a list with itself is a no-op rather than a full rewrite. Repeating an ID in actorIDs is not an error; the list is deduplicated. The returned IDs are sorted, not echoed back in the order they were sent.

type Null

type Null[T any] struct {
	// Value is the value to send. Ignored unless Valid is true.
	Value T
	// Valid reports whether Value should be sent. When false, the field
	// is sent as JSON null.
	Valid bool
}

Null holds an optional, nullable field in an update request.

The API's update endpoints are merge-patch: a field absent from the request body keeps its current value, while an explicit JSON null clears it. A plain Go string can't express both - its zero value is indistinguishable from "not set" - so nullable update fields are typed *Null[T], which has three states:

nil            field omitted; the server keeps its current value
Clear[T]()     field sent as JSON null; the server clears it
Set(v)         field sent as v

Set("") also clears a nullable string field, rather than storing an empty one: the API's changeset treats "" as an empty value and replaces it with the field's default, which for a nullable field is null. Prefer Clear anyway - it says what it means, works for non-string types, and doesn't depend on that coincidence holding.

func Clear

func Clear[T any]() *Null[T]

Clear returns a *Null that sends JSON null, clearing the field on the server. The type parameter is usually explicit, since there's no argument to infer it from: firezone.Clear[string]().

Example

Clear removes a nullable field, which no plain string value can express: a nil pointer means "leave it alone", so there would otherwise be no way to say "set this to nothing".

package main

import (
	"context"
	"log"

	firezone "github.com/firezone/firezone-go"
)

func main() {
	var client *firezone.Client // see [NewClient]

	_, err := client.Resources.Update(context.Background(), "resource-id",
		&firezone.UpdateResourceRequest{
			// Remove the description; leave every other field untouched.
			AddressDescription: firezone.Clear[string](),
			// Move the Resource to another Site.
			SiteID: firezone.Set("site-id"),
		})
	if err != nil {
		log.Fatal(err)
	}
}

func Set

func Set[T any](v T) *Null[T]

Set returns a *Null that sends v.

func (Null[T]) MarshalJSON

func (n Null[T]) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler, encoding an invalid Null as JSON null and a valid one as its value.

func (*Null[T]) UnmarshalJSON

func (n *Null[T]) UnmarshalJSON(data []byte) error

UnmarshalJSON implements json.Unmarshaler. A null decodes to the zero value with Valid false.

Note that encoding/json sets a *Null field to nil on JSON null without calling this method, so decoding cannot tell a cleared field from an omitted one. Null is an encode-side type; the SDK never decodes a request body, and read models use plain fields.

type OIDCAuthProvider

type OIDCAuthProvider struct {
	AuthProvider
	IsDefault               bool   `json:"is_default"`
	ClientID                string `json:"client_id"`
	DiscoveryDocumentURI    string `json:"discovery_document_uri"`
	EmailVerificationMethod string `json:"email_verification_method"`
}

OIDCAuthProvider is a generic OpenID Connect provider.

type OIDCAuthProvidersService

type OIDCAuthProvidersService struct {
	// contains filtered or unexported fields
}

OIDCAuthProvidersService reads generic OIDC auth providers. Read-only - see AuthProvider.

func (*OIDCAuthProvidersService) Get

Get fetches a single OIDC auth provider by ID.

func (*OIDCAuthProvidersService) List

List returns a page of OIDC auth providers. Pass nil for opts to use the API's default page size and no filters.

type OktaAuthProvider

type OktaAuthProvider struct {
	AuthProvider
	IsDefault  bool   `json:"is_default"`
	ClientID   string `json:"client_id"`
	OktaDomain string `json:"okta_domain"`
}

OktaAuthProvider is an Okta sign-in provider.

type OktaAuthProvidersService

type OktaAuthProvidersService struct {
	// contains filtered or unexported fields
}

OktaAuthProvidersService reads Okta auth providers. Read-only - see AuthProvider.

func (*OktaAuthProvidersService) Get

Get fetches a single Okta auth provider by ID.

func (*OktaAuthProvidersService) List

List returns a page of Okta auth providers. Pass nil for opts to use the API's default page size and no filters.

type OktaDirectoriesService

type OktaDirectoriesService struct {
	// contains filtered or unexported fields
}

OktaDirectoriesService reads Okta directory connections. Read-only: there is no Create, Update, or Delete - see OktaDirectory.

func (*OktaDirectoriesService) Get

Get fetches a single Okta directory by ID.

func (*OktaDirectoriesService) List

List returns a page of Okta directories. Pass nil for opts to use the API's default page size.

type OktaDirectory

type OktaDirectory struct {
	ID             string     `json:"id"`
	AccountID      string     `json:"account_id"`
	Name           string     `json:"name"`
	ClientID       string     `json:"client_id"`
	Kid            string     `json:"kid"`
	OktaDomain     string     `json:"okta_domain"`
	IsDisabled     bool       `json:"is_disabled"`
	DisabledReason string     `json:"disabled_reason,omitempty"`
	SyncedAt       *time.Time `json:"synced_at,omitempty"`
	ErrorMessage   string     `json:"error_message,omitempty"`
	ErroredAt      *time.Time `json:"errored_at,omitempty"`
	InsertedAt     time.Time  `json:"inserted_at"`
	UpdatedAt      time.Time  `json:"updated_at"`
}

OktaDirectory is an Okta directory connection. Directories are read-only via this API - they're managed through the Firezone dashboard's identity provider setup, not created or updated here.

type Option

type Option func(*Client) error

Option configures a Client.

An Option returns an error rather than silently accepting bad input, so a misconfiguration is reported by NewClient at construction instead of surfacing later as a failed - or worse, a silently skipped - request. NewClient applies options in order and stops at the first error.

func WithHTTPClient

func WithHTTPClient(hc *http.Client) Option

WithHTTPClient sets the underlying *http.Client used for requests, replacing the default described in NewClient.

Supplying a client is how a caller sets its own timeout, transport, or proxy configuration. Passing nil is an error: it would otherwise panic with a nil dereference on the first request, pointing at the request rather than at the option that caused it.

func WithRequestTimeout

func WithRequestTimeout(d time.Duration) Option

WithRequestTimeout bounds a single HTTP attempt, from dialing to reading the response body. The default is defaultRequestTimeout.

This bounds one attempt, not a whole call: retry waits sit between requests rather than inside one, so a rate-limited call can still take longer overall, up to whatever budget WithRetry allows.

It is applied to the request context rather than to the underlying http.Client, so nothing here overrides anything else. A Timeout on a client passed to WithHTTPClient, a deadline already on the caller's context, and this option all apply together, and whichever expires first ends the attempt.

Zero means the SDK imposes no timeout of its own, leaving the deadline entirely to the caller's context and http.Client. A negative duration is an error - context.WithTimeout would treat it as an already-expired deadline, failing every request before it is sent.

func WithRetry

func WithRetry(enabled bool, maxRetries int) Option

WithRetry configures automatic retry-with-backoff on HTTP 429 (rate limited) responses. Retries are enabled by default with a budget of defaultMaxRetries.

Waits escalate exponentially, never drop below the response's Retry-After header, and always carry jitter so concurrent callers don't retry in lockstep.

maxRetries is the number of retries after the first attempt, so zero means "try once, do not retry". A negative budget is rejected rather than clamped: the retry loop runs maxRetries+1 times, so a negative value would make it run zero times and return no response and no error at all. Nothing a caller can mean by it is worth guessing at.

Example

Retries are on by default and cover HTTP 429 only, honouring the API's Retry-After header. Tune the budget when a large concurrent run exhausts it.

package main

import (
	"log"
	"os"

	firezone "github.com/firezone/firezone-go"
)

func main() {
	client, err := firezone.NewClient("https://api.firezone.dev", os.Getenv("FIREZONE_TOKEN"),
		firezone.WithRetry(true, 20),
	)
	if err != nil {
		log.Fatal(err)
	}
	_ = client
}

func WithRetryMaxWait

func WithRetryMaxWait(d time.Duration) Option

WithRetryMaxWait caps how long any single retry waits, bounding the exponential escalation. Zero or negative restores defaultMaxRetryWait.

Raise it when a large Terraform run is still exhausting its budget: a higher cap buys more total patience per retry than more attempts at a low cap does.

func WithUserAgent

func WithUserAgent(ua string) Option

WithUserAgent replaces the User-Agent header sent with every request. It replaces rather than extends the default, so a caller that wants to keep the SDK's identity should include it:

firezone.WithUserAgent("terraform-provider-firezone/2.1.0 firezone-go-client/" + firezone.Version)

type Page

type Page[T any] struct {
	Data     []T
	Metadata PageMetadata
}

Page is one page of results from a List method.

type PageMetadata

type PageMetadata struct {
	// Count is the total number of items across all pages.
	Count int
	// Limit is the page size that was actually applied.
	Limit int
	// NextPage is the cursor for the next page, or "" if this is the
	// last page.
	NextPage string
	// PrevPage is the cursor for the previous page, or "" if this is
	// the first page.
	PrevPage string
}

PageMetadata describes a page of results returned by a List method.

type PoliciesService

type PoliciesService struct {
	// contains filtered or unexported fields
}

PoliciesService manages Policies.

func (*PoliciesService) Create

Create creates a new Policy.

func (*PoliciesService) Delete

func (s *PoliciesService) Delete(ctx context.Context, id string) error

Delete deletes a Policy.

func (*PoliciesService) Disable

func (s *PoliciesService) Disable(ctx context.Context, id string) (*Policy, error)

Disable disables a Policy, stopping it granting access without deleting it. Idempotent - disabling an already-disabled Policy is a no-op.

This is a convenience wrapper over PoliciesService.Update; the API has no dedicated disable endpoint.

func (*PoliciesService) Enable

func (s *PoliciesService) Enable(ctx context.Context, id string) (*Policy, error)

Enable enables a disabled Policy. Idempotent - enabling an already-enabled Policy is a no-op.

This is a convenience wrapper over PoliciesService.Update; the API has no dedicated enable endpoint.

func (*PoliciesService) Get

func (s *PoliciesService) Get(ctx context.Context, id string) (*Policy, error)

Get fetches a single Policy by ID.

func (*PoliciesService) List

List returns a page of Policies. Pass nil for opts to use the API's default page size and no filters.

func (*PoliciesService) Update

func (s *PoliciesService) Update(ctx context.Context, id string, req *UpdatePolicyRequest) (*Policy, error)

Update updates a Policy.

type Policy

type Policy struct {
	ID                    string      `json:"id"`
	GroupID               string      `json:"group_id"`
	ResourceID            string      `json:"resource_id"`
	Description           string      `json:"description"`
	FlowLogUploadsEnabled bool        `json:"flow_log_uploads_enabled"`
	IsDisabled            bool        `json:"is_disabled"`
	Conditions            []Condition `json:"conditions"`
}

Policy grants a Group access to a Resource, optionally restricted by Conditions.

type PolicyListOptions

type PolicyListOptions struct {
	ListOptions
	// GroupID filters to Policies granting this Group.
	GroupID string
	// ResourceID filters to Policies granting access to this Resource.
	ResourceID string
}

PolicyListOptions extends ListOptions with Policies-specific filters.

type PoolMember

type PoolMember struct {
	ID         string     `json:"id"`
	Name       string     `json:"name"`
	LastSeenAt *time.Time `json:"last_seen_at,omitempty"`
}

PoolMember is a Client's minimal representation as returned by PoolMembersService.List.

Pool members are Client devices, not Actors - a static device pool grants access to specific machines, so this is not the device-shaped equivalent of GroupMember despite the similar surface.

type PoolMembersService

type PoolMembersService struct {
	// contains filtered or unexported fields
}

PoolMembersService manages the membership of a single static_device_pool Resource. Obtain one via ResourcesService.PoolMembers.

Every method returns 400 if the Resource is not a static_device_pool; no other Resource type has members.

func (*PoolMembersService) List

List returns a page of the pool's member Clients.

func (*PoolMembersService) Patch

func (s *PoolMembersService) Patch(ctx context.Context, add, remove []string) ([]string, error)

Patch adds and removes Clients without disturbing any other member of the pool, returning the resulting member Client IDs. This is the safe choice when more than one caller manages membership on the same pool independently.

Both operations are idempotent: adding a Client already in the pool and removing one that isn't are both no-ops. remove is applied before add, so an ID in both slices ends up in the pool.

func (*PoolMembersService) ReplaceAll

func (s *PoolMembersService) ReplaceAll(ctx context.Context, deviceIDs []string) ([]string, error)

ReplaceAll replaces the pool's entire membership with deviceIDs, returning the resulting member Client IDs. Any Client not in deviceIDs is removed from the pool. Passing an empty slice clears it.

Prefer PoolMembersService.Patch when multiple independent callers manage membership on the same pool - ReplaceAll from more than one caller will overwrite each other's changes.

Returns 422 if any ID is not a Client in the account: a Gateway ID, a Client from another account, and a nonexistent ID all fail the same way.

type ProvisionGatewayRequest

type ProvisionGatewayRequest struct {
	Name string `json:"name,omitempty"`
}

ProvisionGatewayRequest is the request body for GatewaysService.Provision. Name is optional; the API generates a random name when omitted.

type ProvisionedGateway

type ProvisionedGateway struct {
	Gateway
	// Token is the one-time Gateway token secret. Store it securely -
	// it cannot be retrieved again.
	Token string `json:"token"`
}

ProvisionedGateway is a newly provisioned Gateway along with its one-time token secret, returned only from GatewaysService.Provision. The API never re-exposes the token after creation - see GatewaysService.Get, which returns a plain Gateway with no token field at all, so the type system itself prevents a caller from expecting a token after a refresh.

type Resource

type Resource struct {
	ID                 string       `json:"id"`
	Name               string       `json:"name"`
	Address            string       `json:"address"`
	AddressDescription string       `json:"address_description"`
	Type               ResourceType `json:"type"`
	IPStack            IPStack      `json:"ip_stack,omitempty"`
	SiteID             string       `json:"site_id,omitempty"`
	Filters            []Filter     `json:"filters"`
}

Resource is a Firezone Resource - a network object (CIDR, IP, DNS name, or static device pool) that Policies grant access to.

type ResourceListOptions

type ResourceListOptions struct {
	ListOptions
	// Name filters to Resources with this exact name.
	Name string
	// Type filters to Resources of this type.
	Type ResourceType
	// SiteID filters to Resources connected to this Site.
	SiteID string
	// Address filters to Resources with this exact address.
	Address string
	// IPStack filters to Resources with this exact ip_stack.
	IPStack IPStack
}

ResourceListOptions extends ListOptions with Resources-specific filters.

type ResourceType

type ResourceType string

ResourceType is the type of network object a Resource represents.

const (
	ResourceTypeCIDR ResourceType = "cidr"
	ResourceTypeIP   ResourceType = "ip"
	ResourceTypeDNS  ResourceType = "dns"

	// ResourceTypeStaticDevicePool is currently readable but not
	// creatable: the API rejects any request that changes a Resource's
	// type to it, on both create and update, with a 422. Create device
	// pools in the admin portal instead.
	//
	// The constant stays because existing pools are still returned by
	// Get and List, still filterable via [ResourceListOptions.Type], and
	// still updatable and deletable - only the transition into this type
	// is refused. Note that restating an existing pool's own type on an
	// update is not a transition and is accepted.
	ResourceTypeStaticDevicePool ResourceType = "static_device_pool"
)

Resource types. "internet" also exists but is API-read-only - the API returns 403 if you try to create or update one - so it's deliberately not offered as a constant here.

type ResourcesService

type ResourcesService struct {
	// contains filtered or unexported fields
}

ResourcesService manages Resources, and, nested under them, static device pool membership.

func (*ResourcesService) Create

Create creates a new Resource.

Two types cannot be created: "internet" (403 Forbidden) and ResourceTypeStaticDevicePool (422) - create device pools in the admin portal instead.

func (*ResourcesService) Delete

func (s *ResourcesService) Delete(ctx context.Context, id string) error

Delete deletes a Resource.

func (*ResourcesService) Get

func (s *ResourcesService) Get(ctx context.Context, id string) (*Resource, error)

Get fetches a single Resource by ID.

func (*ResourcesService) List

List returns a page of Resources. Pass nil for opts to use the API's default page size and no filters.

func (*ResourcesService) PoolMembers

func (s *ResourcesService) PoolMembers(resourceID string) *PoolMembersService

PoolMembers returns a PoolMembersService scoped to the static_device_pool Resource identified by resourceID. Calling it for any other Resource type is allowed, but every request that service makes will fail with 400.

func (*ResourcesService) Update

Update updates a Resource.

Changing a Resource's type to ResourceTypeStaticDevicePool is refused with a 422, the same as creating one. Restating an existing pool's own type is not a change and is accepted, so a caller that echoes the whole Resource back on update still works.

Example

Update requests are merge-patch: a field left nil keeps its current value, so this renames a Resource without disturbing anything else.

package main

import (
	"context"
	"fmt"
	"log"

	firezone "github.com/firezone/firezone-go"
)

func main() {
	var client *firezone.Client // see [NewClient]

	updated, err := client.Resources.Update(context.Background(), "resource-id",
		&firezone.UpdateResourceRequest{Name: "postgres-prod"})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(updated.Name)
}

type RotatedGatewayToken

type RotatedGatewayToken struct {
	// ID is the new token's ID, which becomes the Gateway's
	// GatewayTokenID once it connects with this token.
	ID string `json:"id"`
	// Token is the replacement secret. Store it securely - it cannot be
	// retrieved again.
	Token string `json:"token"`
}

RotatedGatewayToken is the replacement token minted by GatewaysService.RotateToken. Like ProvisionedGateway.Token, the secret is shown exactly once.

type Site

type Site struct {
	ID   string `json:"id"`
	Name string `json:"name"`
}

Site is a Firezone Site - a logical grouping of Gateways and the Resources they expose.

type SiteListOptions

type SiteListOptions struct {
	ListOptions
	// Name filters to the Site with this exact name.
	Name string
}

SiteListOptions extends ListOptions with Sites-specific filters.

type SitesService

type SitesService struct {
	// contains filtered or unexported fields
}

SitesService manages Sites, and, nested under them, Gateways.

func (*SitesService) Create

func (s *SitesService) Create(ctx context.Context, req *CreateSiteRequest) (*Site, error)

Create creates a new Site.

func (*SitesService) Delete

func (s *SitesService) Delete(ctx context.Context, id string) error

Delete deletes a Site.

func (*SitesService) Gateways

func (s *SitesService) Gateways(siteID string) *GatewaysService

Gateways returns a GatewaysService scoped to the Site identified by siteID, matching the API's own URL nesting (/sites/{site_id}/gateways/...).

Example

Gateways are nested under their Site, matching the API's own URLs. The token comes back exactly once, on provisioning.

package main

import (
	"context"
	"fmt"
	"log"

	firezone "github.com/firezone/firezone-go"
)

func main() {
	var client *firezone.Client // see [NewClient]

	gateway, err := client.Sites.Gateways("site-id").Provision(context.Background(),
		&firezone.ProvisionGatewayRequest{Name: "gw-nyc-1"})
	if err != nil {
		log.Fatal(err)
	}

	// Store this now - the API never exposes it again.
	fmt.Println(gateway.Token)
}

func (*SitesService) Get

func (s *SitesService) Get(ctx context.Context, id string) (*Site, error)

Get fetches a single Site by ID.

func (*SitesService) List

func (s *SitesService) List(ctx context.Context, opts *SiteListOptions) (*Page[Site], error)

List returns a page of Sites. Pass nil for opts to use the API's default page size and no filters.

func (*SitesService) Update

func (s *SitesService) Update(ctx context.Context, id string, req *UpdateSiteRequest) (*Site, error)

Update updates a Site.

type UpdateActorRequest

type UpdateActorRequest struct {
	Name string    `json:"name,omitempty"`
	Type ActorType `json:"type,omitempty"`
	// Email is nullable, so it is typed [Null] - Clear[string]() removes
	// the Actor's email, and a nil pointer leaves it alone.
	Email               *Null[string] `json:"email,omitempty"`
	AllowEmailOTPSignIn *bool         `json:"allow_email_otp_sign_in,omitempty"`
	IsDisabled          *bool         `json:"is_disabled,omitempty"`
}

UpdateActorRequest is the request body for ActorsService.Update. Every field is optional; omitted fields keep their current value.

Changing Email to a different address signs the Actor out and unlinks their identity providers - see the API's actor update documentation for details.

Setting IsDisabled to true immediately revokes the Actor's active Client tokens and portal sessions. The API returns 403 Forbidden if it names the Actor behind the calling token - an Actor cannot disable itself.

type UpdateClientRequest

type UpdateClientRequest struct {
	Name string `json:"name"`
}

UpdateClientRequest is the request body for ClientsService.Update. Name is the only mutable field, and the API requires it, so it is always sent - omitting it on an empty value would produce a body the API rejects for a reason that doesn't name the field.

type UpdateGatewayRequest

type UpdateGatewayRequest struct {
	Name string `json:"name"`
}

UpdateGatewayRequest is the request body for GatewaysService.Update. Name is the only mutable field - a Gateway's Site is permanent.

type UpdateGroupRequest

type UpdateGroupRequest struct {
	Name string `json:"name,omitempty"`
}

UpdateGroupRequest is the request body for GroupsService.Update.

type UpdatePolicyRequest

type UpdatePolicyRequest struct {
	GroupID    string `json:"group_id,omitempty"`
	ResourceID string `json:"resource_id,omitempty"`
	// Description is nullable, so it is typed [Null] - Clear[string]()
	// removes it, and a nil pointer leaves it alone.
	Description           *Null[string] `json:"description,omitempty"`
	FlowLogUploadsEnabled *bool         `json:"flow_log_uploads_enabled,omitempty"`
	IsDisabled            *bool         `json:"is_disabled,omitempty"`
	// Conditions replaces the Policy's conditions wholesale. nil leaves
	// them unchanged; a pointer to an empty slice removes all of them,
	// making the Policy grant access unconditionally.
	Conditions *[]Condition `json:"conditions,omitempty"`
}

UpdatePolicyRequest is the request body for PoliciesService.Update. Every field is optional; omitted fields keep their current value.

Setting IsDisabled to true stops the Policy granting access without deleting it.

Example (Conditions)

The embedded lists are pointers for the same reason: a nil pointer leaves them alone, and a pointer to an empty slice removes them all.

package main

import (
	"context"
	"log"

	firezone "github.com/firezone/firezone-go"
)

func main() {
	var client *firezone.Client // see [NewClient]

	// Restrict the Policy to two countries.
	_, err := client.Policies.Update(context.Background(), "policy-id",
		&firezone.UpdatePolicyRequest{
			Conditions: &[]firezone.Condition{{
				Property: firezone.ConditionPropertyRemoteIPLocationRegion,
				Operator: firezone.ConditionOperatorIsIn,
				Values:   []string{"US", "CA"},
			}},
		})
	if err != nil {
		log.Fatal(err)
	}

	// Remove every condition, granting access unconditionally.
	if _, err := client.Policies.Update(context.Background(), "policy-id",
		&firezone.UpdatePolicyRequest{Conditions: &[]firezone.Condition{}}); err != nil {
		log.Fatal(err)
	}
}

type UpdateResourceRequest

type UpdateResourceRequest struct {
	Name    string        `json:"name,omitempty"`
	Type    ResourceType  `json:"type,omitempty"`
	Address *Null[string] `json:"address,omitempty"`
	// AddressDescription is free-form text describing the address.
	// Clear[string]() removes it. Set("") removes it too - the API
	// replaces an empty string with the field's default rather than
	// storing it - but Clear states the intent.
	AddressDescription *Null[string]  `json:"address_description,omitempty"`
	IPStack            *Null[IPStack] `json:"ip_stack,omitempty"`
	// SiteID moves the Resource to another Site. Clearing it detaches
	// the Resource from its Site, which the API only permits for device
	// pool Resources.
	SiteID *Null[string] `json:"site_id,omitempty"`
	// Filters replaces the Resource's filters wholesale. nil leaves them
	// unchanged; a pointer to an empty slice removes all of them.
	Filters *[]Filter `json:"filters,omitempty"`
}

UpdateResourceRequest is the request body for ResourcesService.Update. All fields are optional; omitted fields keep their current value.

The nullable fields are typed Null so they can be cleared as well as set - see that type for the three states. Filters is a pointer to a slice for the same reason: a nil pointer leaves the Resource's filters alone, while a pointer to an empty slice removes all of them.

type UpdateSiteRequest

type UpdateSiteRequest struct {
	Name string `json:"name,omitempty"`
}

UpdateSiteRequest is the request body for SitesService.Update. All fields are optional; omitted fields keep their current value.

Directories

Path Synopsis
internal
testutil
Package testutil provides shared httptest helpers for table-driven api-client tests.
Package testutil provides shared httptest helpers for table-driven api-client tests.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL