One Identity Safeguard SDK for Go.
safeguard-go is the standalone Go SDK for One Identity Safeguard for Privileged Passwords (SPP). It mirrors the SafeguardDotNet and PySafeguard SDKs with a Go-idiomatic API surface for authentication, Invoke-style API calls, Application-to-Application (A2A) workflows, and events.
The module is pre-1.0; the public API may change before the first v1.0.0 release. See docs/versioning.md for the versioning and release policy.
- Authentication — Resource Owner Grant username/password, certificate login over mutual TLS, PKCE headless (with MFA/secondary-factor support), existing user token, and anonymous access. Interactive external-browser and device-code logins ship as optional add-on packages so headless consumers keep a lean dependency graph.
- Invoke and Service surface —
Connect,Get/Post/Put/Deletehelpers, genericInvoke, typedInvokeTyped[T], query parameters, headers, per-request API-version and host overrides,Response, andStream/Upload/Downloadfor large payloads. - Application-to-Application (A2A) — password, SSH private-key, and API-key secret retrieval; password and private-key set (write-back); access-request brokering; retrievable-account discovery; and credential-change events.
- Events — an owned SignalR-over-WebSocket implementation with one-shot and persistent (auto-reconnecting) listeners for both user sessions and A2A credential-change streams.
- TLS secure by default — HTTPS-only appliance endpoints (a non-https host is rejected), system trust by default, custom CA bundles that replace the system trust store, an additive server-certificate validator callback, and a loud insecure override for bootstrap/test appliances.
- Typed errors — API, authentication (401), authorization (403), not-found (404), transport, and sentinel errors designed for
errors.Is/errors.As. - Secret hygiene — the
Secrettype wraps sensitive values, redacts them from strings/logs/errors/JSON, copies on construction and exposure, and best-effort zeroes on close.
The only third-party dependency is github.com/coder/websocket (used by the event listeners). The browser and devicecode add-ons are separate packages, so importing the root package alone stays dependency-light.
go get github.com/OneIdentity/safeguard-goThe SDK requires Go 1.21 or later.
package main
import (
"context"
"log"
"github.com/OneIdentity/safeguard-go"
)
func main() {
ctx := context.Background()
cred := safeguard.UsernamePassword(
"local",
"Admin",
safeguard.NewSecretString("correct horse battery staple"),
)
client, err := safeguard.Connect(ctx, "safeguard.sample.corp", cred)
if err != nil {
log.Fatal(err)
}
defer client.Close()
resp, err := client.Get(ctx, safeguard.Core, "Me")
if err != nil {
log.Fatal(err)
}
log.Printf("GET Me -> %d\n%s\n", resp.StatusCode, resp.Body)
}Recent Safeguard appliances commonly disable Resource Owner Grant (ROG). New applications should prefer PKCE, browser, device-code, certificate, or token-based flows as appropriate. PKCEHeadless is the recommended flow for automation because it does not depend on ROG.
To decode a JSON response into a Go value, use the generic helper:
type me struct {
UserName string `json:"UserName"`
DisplayName string `json:"DisplayName"`
}
info, err := safeguard.InvokeTyped[me](ctx, client, safeguard.MethodGet, safeguard.Core, "Me", nil)
if err != nil {
log.Fatal(err)
}
log.Printf("signed in as %s (%s)", info.UserName, info.DisplayName)The samples directory has a small, standalone program for each flow — every authentication method, the A2A retrieval/set/broker workflows, and the event listeners. Each sample takes its inputs as flags; run one with, for example:
go run ./samples/pkce -appliance safeguard.example.com -username Admin -insecureSee samples/README.md for the full list and the shared flags.
The SDK targets Safeguard API v4 by default for Safeguard 7.0 and later. Override it per client with WithAPIVersion or per request with WithAPIVersionOverride (for example "v3" for legacy compatibility); the SDK does not auto-detect the API version.
Safeguard for Privileged Passwords 9.0 enables TLS 1.3. safeguard-go works against both 8.x and 9.0 with no configuration changes and is secure by default: every transport negotiates TLS 1.2 or higher.
The negotiated protocol window defaults to a TLS 1.2 floor on every transport, with the ceiling chosen per transport. Password, token, and PKCE traffic (the server-trust transport) is free to negotiate TLS 1.3 where the appliance supports it, while certificate login and A2A (the client-certificate transport) are capped at TLS 1.2.
Certificate authentication requires the client to present its certificate in response to a request the appliance makes after the handshake. Go's TLS stack answers that only at TLS 1.2 (via renegotiation); it neither offers the TLS 1.3 post_handshake_auth extension nor answers a post-handshake CertificateRequest, so a TLS 1.3 certificate-auth attempt against the appliance Standard binding fails with 60094 Authorization is denied. Capping the certificate transport at TLS 1.2 keeps certificate and A2A auth working out of the box while everything else still gets TLS 1.3. That transport also uses HTTP/1.1 (the HTTP/2 upgrade is disabled), because HTTP/2 disallows the post-handshake certificate exchange.
WithMinTLSVersion and WithMaxTLSVersion take a crypto/tls version constant and are passed to Connect. Values pass straight through to crypto/tls, so protocol versions added in the future need no SDK change.
import (
"crypto/tls"
"github.com/OneIdentity/safeguard-go"
)
// Require TLS 1.3 on the Standard host (fail closed against anything lower).
// This enforces 1.3 for password/token/PKCE auth; certificate and A2A auth
// need the Cert SNI host instead — see the third example below.
client, err := safeguard.Connect(ctx, "safeguard.sample.corp", cred,
safeguard.WithMinTLSVersion(tls.VersionTLS13))
// Pin the connection to TLS 1.2 (interim, for environments not ready for 1.3)
client, err = safeguard.Connect(ctx, "safeguard.sample.corp", cred,
safeguard.WithMaxTLSVersion(tls.VersionTLS12))
// Certificate/A2A auth over TLS 1.3: connect to the appliance Cert SNI
// hostname (NOT the Standard host, which caps cert auth at TLS 1.2 and would
// fail closed here). Setting a bound lifts the automatic cert-transport cap;
// the SNI binding requests the client certificate in-handshake, which Go can
// answer at TLS 1.3.
client, err = safeguard.Connect(ctx, "spp-tls13.sample.corp", cred,
safeguard.WithMinTLSVersion(tls.VersionTLS13))Setting either bound turns off the automatic TLS 1.2 cap on the certificate transport, putting you fully in control. This is what enables certificate and A2A authentication over TLS 1.3, but it only works against the appliance Cert SNI hostname — a binding that requests the client certificate during the handshake, which Go's TLS stack can answer at TLS 1.3. Pointing WithMinTLSVersion(tls.VersionTLS13) at the Standard host instead (as in the first example above) makes certificate and A2A auth fail closed with 60094 Authorization is denied, because that binding still relies on the post-handshake certificate request Go only answers at TLS 1.2. Password, token, and PKCE auth negotiate TLS 1.3 on either host. A maximum lower than the minimum is rejected at connect time, and lowering the minimum below TLS 1.2 is a discouraged escape hatch for legacy interoperability that should never be used in production.
The default (no options) negotiates the highest mutually supported version and works against both 8.x (TLS 1.2 ceiling) and 9.0 (TLS 1.3) with no flags. Requiring TLS 1.3 against an appliance that tops out at TLS 1.2 (such as SPP 8.x) fails the handshake by design rather than silently downgrading. Server-certificate trust is configured separately with WithCABundle, WithServerCertValidator, and the bootstrap-only WithInsecureTLS.
One Identity open source projects are supported through GitHub issues and the One Identity Community. Open an issue in this repository to report a bug or request a feature.