Documentation
¶
Overview ¶
Package config is the funcd daemon config — funcdconfig.yaml (ADR-0061, ADR-0062). It is ONE struct populated from file + env + default and validated once:
defaults() → yaml.UnmarshalStrict (file) → env.Parse (FUNCD_* overlay) → --memory flag → Validate
precedence flag > env > file > default. A key is defined in one place — a single Config field with three tags: `json` is the yaml key (sigs.k8s.io/yaml maps yaml→json→struct, reusing json tags as in ADR-0061), `env` is the FUNCD_* overlay var (caarlos0/env, which leaves a field untouched when its var is unset — no clobber), `validate` is the rule (go-playground/validator, the lib huma already pulls). Validation runs on the MERGED struct, so a bad value from any source is caught. It is a leaf (no driver imports); cmd/funcd maps Config → []funcd.Option.
Index ¶
Constants ¶
const ( APIVersion = "funcd.io/v1alpha1" Kind = "FuncdConfig" )
APIVersion / Kind are the optional funcdconfig.yaml envelope (validated only when present).
const DefaultImagePrefix = "funcd/runtime-"
DefaultImagePrefix is the built-in runtime.containerd.imagePrefix, the repository the embedded curated runtime images are tagged under. With it only the embedded images are used: a runtime image is pulled only from a registry the operator chose, an imageOverride entry or a custom imagePrefix.
Variables ¶
This section is empty.
Functions ¶
func Locate ¶
Locate returns the config file path to load: explicit (--config; must exist → else fault.NotFound) → $FUNCD_CONFIG (must exist) → first existing of ./funcdconfig.yaml, /etc/funcd/funcdconfig.yaml → "" (none; zero-config). An explicit/env path that doesn't exist is an error; the implicit search paths may be absent.
Types ¶
type Config ¶
type Config struct {
APIVersion string `json:"apiVersion,omitempty" validate:"omitempty,eq=funcd.io/v1alpha1"`
Kind string `json:"kind,omitempty" validate:"omitempty,eq=FuncdConfig"`
Server struct {
ListenAddr string `json:"listenAddr,omitempty" env:"FUNCD_LISTEN_ADDR"`
DataPlaneAddr string `json:"dataPlaneAddr,omitempty" env:"FUNCD_DATA_PLANE_ADDR"`
// TLS termination (ADR-0111, F74). When Enabled, both listeners serve HTTPS via ServeTLS.
// Mode defaults to selfsigned (stdlib, offline). Empty/Enabled=false ⇒ plaintext (default).
TLS struct {
Enabled bool `json:"enabled,omitempty" env:"FUNCD_TLS_ENABLED"`
Mode string `json:"mode,omitempty" env:"FUNCD_TLS_MODE" validate:"omitempty,oneof=selfsigned provided acme"`
Hosts []string `json:"hosts,omitempty" env:"FUNCD_TLS_HOSTS" envSeparator:","`
CertFile string `json:"certFile,omitempty" env:"FUNCD_TLS_CERT_FILE"`
KeyFile string `json:"keyFile,omitempty" env:"FUNCD_TLS_KEY_FILE"`
Email string `json:"email,omitempty" env:"FUNCD_TLS_EMAIL"`
CADir string `json:"caDir,omitempty" env:"FUNCD_TLS_CA_DIR"`
} `json:"tls,omitempty"`
// Ingress protection limits (ADR-0112, F75). The rate, size and in-flight limits are off at 0; maxKeys is
// a table size (min 1, default 4096) with no off switch (ADR-0164).
Limits struct {
RatePerMin int `json:"ratePerMin,omitempty" env:"FUNCD_LIMITS_RATE_PER_MIN" validate:"min=0"`
Burst int `json:"burst,omitempty" env:"FUNCD_LIMITS_BURST" validate:"min=0"`
Key string `json:"key,omitempty" env:"FUNCD_LIMITS_KEY" validate:"omitempty,oneof=clientIP function"`
MaxBodyBytes int64 `json:"maxBodyBytes,omitempty" env:"FUNCD_LIMITS_MAX_BODY_BYTES" validate:"min=0"`
MaxInFlight int `json:"maxInFlight,omitempty" env:"FUNCD_LIMITS_MAX_IN_FLIGHT" validate:"min=0"`
MaxKeys int `json:"maxKeys,omitempty" env:"FUNCD_LIMITS_MAX_KEYS" validate:"min=1"`
} `json:"limits,omitempty"`
// Edge authn PEP (ADR-0113, F77): enable per-target auth-stance enforcement on the data plane.
Auth struct {
Edge bool `json:"edge,omitempty" env:"FUNCD_AUTH_EDGE"`
} `json:"auth,omitempty"`
// Edge observability (ADR-0114, F76): RED metrics + edge trace span + access log. Off by default.
Observability struct {
Metrics bool `json:"metrics,omitempty" env:"FUNCD_OBS_METRICS"`
AccessLog bool `json:"accessLog,omitempty" env:"FUNCD_OBS_ACCESS_LOG"`
Trace bool `json:"trace,omitempty" env:"FUNCD_OBS_TRACE"`
} `json:"observability,omitempty"`
// Edge shaping (ADR-0114, F78): CORS / response headers / gzip compression. Off by default.
Shaping struct {
CORS struct {
AllowOrigins []string `json:"allowOrigins,omitempty" env:"FUNCD_SHAPING_CORS_ALLOW_ORIGINS" envSeparator:","`
AllowMethods []string `json:"allowMethods,omitempty" env:"FUNCD_SHAPING_CORS_ALLOW_METHODS" envSeparator:","`
AllowHeaders []string `json:"allowHeaders,omitempty" env:"FUNCD_SHAPING_CORS_ALLOW_HEADERS" envSeparator:","`
MaxAgeSeconds int `json:"maxAgeSeconds,omitempty" env:"FUNCD_SHAPING_CORS_MAX_AGE_SECONDS" validate:"min=0"`
} `json:"cors,omitempty"`
Headers struct {
// The env form splits pairs on ",", so a header value that contains a comma needs the file.
Set map[string]string `` /* 133-byte string literal not displayed */
Remove []string `json:"remove,omitempty" env:"FUNCD_SHAPING_HEADERS_REMOVE" envSeparator:","`
} `json:"headers,omitempty"`
Compression bool `json:"compression,omitempty" env:"FUNCD_SHAPING_COMPRESSION"`
} `json:"shaping,omitempty"`
// Egress network isolation (ADR-0115, FEAT-0007/F80): the worker-egress default-deny substrate.
// Off by default (phased); Linux/containerd only (a no-op elsewhere). When enabled, funcd programs
// an nftables policy over the funcd0 bridge that default-denies worker egress and redirects
// remaining external TCP into the egress gateway (F81). DNSResolver / InternalAllow are the
// worker-reachable resolver + node-service addresses (host:port) passed directly.
Network struct {
Egress bool `json:"egress,omitempty" env:"FUNCD_NETWORK_EGRESS"`
EgressGatewayPort int `json:"egressGatewayPort,omitempty" env:"FUNCD_NETWORK_EGRESS_GATEWAY_PORT" validate:"min=0,max=65535"`
// DNSForwarderPort is the funcd DNS forwarder host port (ADR-0117, F81): worker :53 is
// REDIRECTed into it so it is the only reachable resolver (the domain trust anchor). Wired
// with the gateway when Egress is on (Linux/containerd only).
DNSForwarderPort int `json:"dnsForwarderPort,omitempty" env:"FUNCD_NETWORK_DNS_FORWARDER_PORT" validate:"min=0,max=65535"`
DNSResolver string `json:"dnsResolver,omitempty" env:"FUNCD_NETWORK_DNS_RESOLVER"`
InternalAllow []string `json:"internalAllow,omitempty" env:"FUNCD_NETWORK_INTERNAL_ALLOW" envSeparator:","`
} `json:"network,omitempty"`
} `json:"server,omitempty"`
Storage struct {
Mode string `json:"mode,omitempty" env:"FUNCD_STORAGE_MODE" validate:"oneof=file memory"`
DataDir string `json:"dataDir,omitempty" env:"FUNCD_DATA_DIR" validate:"required"`
// MetastoreDir is the control-plane metastore's dedicated Badger directory (ADR-0065). Empty ⇒
// derived as <DataDir>/store in Load(); ignored (in-memory engine) when Mode is memory. Its own
// instance, never shared — see the store-per-service rule (restore class + service ownership).
MetastoreDir string `json:"metastoreDir,omitempty" env:"FUNCD_METASTORE_DIR"`
} `json:"storage,omitempty"`
Kvstore struct {
// Engine for the function-facing KV service (ADR-0066/0069): memory (default, ephemeral) or
// badger (durable). Its own dedicated instance. DataDir is empty ⇒ derived as <Storage.DataDir>/kv
// in Load(); an explicit value overrides the default location. Ignored (in-memory) when Storage.Mode
// is memory.
Engine string `json:"engine,omitempty" env:"FUNCD_KVSTORE_ENGINE" validate:"omitempty,oneof=memory badger"`
DataDir string `json:"dataDir,omitempty" env:"FUNCD_KVSTORE_DATA_DIR"`
// MaxStoresPerNamespace is the per-namespace KVStore count cap enforced at admission (ADR-0072);
// 0 ⇒ the default (100); a negative value disables the quota.
MaxStoresPerNamespace int `json:"maxStoresPerNamespace,omitempty" env:"FUNCD_KVSTORE_MAX_STORES_PER_NAMESPACE"`
// Backup is the opt-in DR export of the KV instance to object storage (ADR-0067), off by default.
// Enabled without a Target, or with Engine memory ⇒ fault.Invalid at startup; ignored with a warning when
// Storage.Mode is memory. Interval/Rebaseline are Go durations ("30s").
Backup struct {
Enabled bool `json:"enabled,omitempty" env:"FUNCD_KVSTORE_BACKUP_ENABLED"`
Target string `json:"target,omitempty" env:"FUNCD_KVSTORE_BACKUP_TARGET"`
Interval string `json:"interval,omitempty" env:"FUNCD_KVSTORE_BACKUP_INTERVAL"`
Rebaseline string `json:"rebaseline,omitempty" env:"FUNCD_KVSTORE_BACKUP_REBASELINE"`
ChunkBytes int `json:"chunkBytes,omitempty" env:"FUNCD_KVSTORE_BACKUP_CHUNK_BYTES" validate:"min=0"`
} `json:"backup,omitempty"`
// Cdc is the opt-in change-feed of the KV instance to the bus (ADR-0068), off by default. Enabled
// without a Sink, or with Engine memory ⇒ fault.Invalid at startup; ignored with a warning when Storage.Mode
// is memory. Retention is a Go duration ("24h").
Cdc struct {
Enabled bool `json:"enabled,omitempty" env:"FUNCD_KVSTORE_CDC_ENABLED"`
Sink string `json:"sink,omitempty" env:"FUNCD_KVSTORE_CDC_SINK"`
Retention string `json:"retention,omitempty" env:"FUNCD_KVSTORE_CDC_RETENTION"`
} `json:"cdc,omitempty"`
} `json:"kvstore,omitempty"`
Auth struct {
Token string `json:"token,omitempty" env:"FUNCD_TOKEN"`
Namespaces []string `json:"namespaces,omitempty" env:"FUNCD_AUTH_NAMESPACES" envSeparator:","`
// Credentials is file-only (ADR-0171 Decision 1): caarlos0/env walks a slice of structs under an
// empty prefix, so stray 0_* variables would add zero-value entries.
Credentials CredentialList `json:"credentials,omitempty" env:"-" validate:"dive"`
} `json:"auth,omitempty"`
Secrets struct {
EncryptionKeyFile string `json:"encryptionKeyFile,omitempty" env:"FUNCD_SECRETS_ENCRYPTION_KEY_FILE"`
} `json:"secrets,omitempty"`
Runtime struct {
Mode string `json:"mode,omitempty" env:"FUNCD_RUNTIME" validate:"oneof=process containerd"`
// BootBackoffInitial and BootBackoffMax bound the wait before a worker that crashed while booting is created
// again (ADR-0160): positive Go durations, the max at least the initial wait.
BootBackoffInitial string `json:"bootBackoffInitial,omitempty" env:"FUNCD_RUNTIME_BOOT_BACKOFF_INITIAL"`
BootBackoffMax string `json:"bootBackoffMax,omitempty" env:"FUNCD_RUNTIME_BOOT_BACKOFF_MAX"`
// Process tunes the process driver (ADR-0167): StopGrace is the wait after SIGTERM before SIGKILL for a stop,
// the shutdown close and the boot reap, a Go duration with 0 < d <= 10s.
Process struct {
StopGrace string `json:"stopGrace,omitempty" env:"FUNCD_PROCESS_STOP_GRACE"`
} `json:"process,omitempty"`
Containerd struct {
Socket string `json:"socket,omitempty" env:"FUNCD_CONTAINERD_SOCKET"`
Root string `json:"root,omitempty" env:"FUNCD_CONTAINERD_ROOT"`
Snapshotter string `json:"snapshotter,omitempty" env:"FUNCD_SNAPSHOTTER"`
CNIBinDir string `json:"cniBinDir,omitempty" env:"FUNCD_CNI_BIN_DIR"`
CNIConfDir string `json:"cniConfDir,omitempty" env:"FUNCD_CNI_CONF_DIR"`
StateDir string `json:"stateDir,omitempty" env:"FUNCD_CONTAINERD_STATE_DIR"`
SubnetCIDR string `json:"subnetCIDR,omitempty" env:"FUNCD_SUBNET_CIDR"`
ImagePrefix string `json:"imagePrefix,omitempty" env:"FUNCD_IMAGE_PREFIX"`
ImageOverride map[string]string `json:"imageOverride,omitempty" env:"FUNCD_IMAGE_OVERRIDE" envSeparator:"," envKeyValSeparator:"="`
} `json:"containerd,omitempty"`
} `json:"runtime,omitempty"`
// Controller tunes the platform controllers; GCSweepInterval is the owner garbage collector's sweep period
// (ADR-0170), a positive Go duration.
Controller struct {
GCSweepInterval string `json:"gcSweepInterval,omitempty" env:"FUNCD_CONTROLLER_GC_SWEEP_INTERVAL"`
} `json:"controller,omitempty"`
Log struct {
Format string `json:"format,omitempty" env:"FUNCD_LOG_FORMAT" validate:"oneof=json text"`
Level string `json:"level,omitempty" env:"FUNCD_LOG_LEVEL" validate:"oneof=debug info warn error"`
} `json:"log,omitempty"`
Telemetry struct {
Endpoint string `json:"endpoint,omitempty" env:"FUNCD_TELEMETRY_ENDPOINT"`
Insecure bool `json:"insecure,omitempty" env:"FUNCD_TELEMETRY_INSECURE"`
} `json:"telemetry,omitempty"`
// Funclog tunes structured function-log capture (ADR-0081) and its traces signal (ADR-0101); both on by
// default. A zero SegmentMaxBytes / empty SegmentMaxAge (a Go duration, "10s") keeps the sink default
// (8 MiB / 10s). The sink writes to the blob substrate, so funcd-system is the only Bucket accepted.
Funclog struct {
Enabled bool `json:"enabled,omitempty" env:"FUNCD_FUNCLOG_ENABLED"`
SegmentMaxBytes int `json:"segmentMaxBytes,omitempty" env:"FUNCD_FUNCLOG_SEGMENT_MAX_BYTES" validate:"min=0"`
SegmentMaxAge string `json:"segmentMaxAge,omitempty" env:"FUNCD_FUNCLOG_SEGMENT_MAX_AGE"`
Bucket string `json:"bucket,omitempty" env:"FUNCD_FUNCLOG_BUCKET" validate:"omitempty,eq=funcd-system"`
Traces bool `json:"traces,omitempty" env:"FUNCD_FUNCLOG_TRACES"`
} `json:"funclog,omitempty"`
// S3Gateway is the opt-in S3-protocol frontend over the blob substrate (ADR-0080/0085).
// Disabled by default ⇒ no listener, no IAM, no keypair injection.
S3Gateway struct {
Enabled bool `json:"enabled,omitempty" env:"FUNCD_S3GATEWAY_ENABLED"`
ListenAddr string `json:"listenAddr,omitempty" env:"FUNCD_S3GATEWAY_LISTEN_ADDR"`
// Endpoint is the sandbox-facing S3 URL injected into a spec.blob worker's AWS_ENDPOINT_URL_S3
// (ADR-0085). Empty ⇒ derived as http://<ListenAddr>. Under containerd a worker is in its own
// netns, so this MUST be a node address the sandbox can reach (the CNI bridge gateway IP, e.g.
// http://10.63.0.1:9000), NOT a 127.0.0.1 ListenAddr.
Endpoint string `json:"endpoint,omitempty" env:"FUNCD_S3GATEWAY_ENDPOINT"`
MaxUploadBytes int64 `json:"maxUploadBytes,omitempty" env:"FUNCD_S3GATEWAY_MAX_UPLOAD_BYTES" validate:"min=0"`
MasterSecretFile string `json:"masterSecretFile,omitempty" env:"FUNCD_S3GATEWAY_MASTER_SECRET_FILE"`
} `json:"s3gateway,omitempty"`
// Catalog tunes the per-CatalogService catalog::query PEP proxies (ADR-0137). ProxyHost is the
// netns-reachable host each proxy publishes into a function's FUNCD_CATALOG_<ALIAS>_URL — the
// query-path analog of S3Gateway.Endpoint's host. Under containerd a worker runs in its own netns
// and cannot reach the daemon's 127.0.0.1, so this MUST be a node address the sandbox can reach (the
// CNI bridge gateway IP, e.g. 10.63.0.1); the proxy then binds 0.0.0.0. Empty ⇒ 127.0.0.1 (the
// process-runtime/dev default, shared loopback).
Catalog struct {
ProxyHost string `json:"proxyHost,omitempty" env:"FUNCD_CATALOG_PROXY_HOST"`
} `json:"catalog,omitempty"`
// Workflow tunes the workflow engine (ADR-0094). Durable run state lives in its own dedicated Badger
// instance at Workflow.DataDir (default <Storage.DataDir>/workflow; in-memory when Storage.Mode is
// memory). DefaultStepTimeout + DefaultRetry feed the engine core. Retention is the terminal-run GC
// horizon: a periodic sweep reclaims each closed run (engine record and WorkflowRun object) older than
// it; 0 ⇒ no sweep. PayloadLimit caps the bytes of a run input (at WorkflowRun admission and run start)
// and of each step output; 0 ⇒ unbounded.
Workflow struct {
DefaultStepTimeout string `json:"defaultStepTimeout,omitempty" env:"FUNCD_WORKFLOW_DEFAULT_STEP_TIMEOUT"`
DefaultRetry int `json:"defaultRetry,omitempty" env:"FUNCD_WORKFLOW_DEFAULT_RETRY" validate:"min=0"`
Retention string `json:"retention,omitempty" env:"FUNCD_WORKFLOW_RETENTION"`
PayloadLimit int64 `json:"payloadLimit,omitempty" env:"FUNCD_WORKFLOW_PAYLOAD_LIMIT" validate:"min=0"`
// MaxStepsInFlight bounds the function-step calls in flight across all runs (ADR-0146); 0 ⇒ no cap.
MaxStepsInFlight int `json:"maxStepsInFlight,omitempty" env:"FUNCD_WORKFLOW_MAX_STEPS_IN_FLIGHT" validate:"min=0"`
// DataDir is the run-state Badger directory. Empty ⇒ derived as <Storage.DataDir>/workflow in
// Load(); ignored (in-memory) when Storage.Mode is memory. An explicit value overrides it.
DataDir string `json:"dataDir,omitempty" env:"FUNCD_WORKFLOW_DATA_DIR"`
} `json:"workflow,omitempty"`
// Eventing tunes the Sensor action-delivery reliability path (ADR-0118, F85). DeliveryAttempts is the
// bounded-retry cap before a failed workflow:/function: delivery is dead-lettered. The dead-letter
// queue is its own dedicated Badger instance at Deadletter.DataDir (default <Storage.DataDir>/deadletter;
// in-memory when Storage.Mode is memory); Deadletter.Retention (TTL) and Deadletter.MaxEntries
// (per-namespace count cap) drive the periodic retention sweep. MaxDeliveriesInFlight (the delivery
// workers), MaxInFlightPerTarget and MaxQueuedPerSensor size the Sensor delivery queue (ADR-0156); each is
// at least 1 and the per-target cap at most the workers. MaxDeliveriesInFlight is declared before
// MaxInFlightPerTarget so a bad worker count is the error reported, not the cap's cross-field check.
Eventing struct {
DeliveryAttempts int `json:"deliveryAttempts,omitempty" env:"FUNCD_EVENTING_DELIVERY_ATTEMPTS" validate:"min=0"`
MaxDeliveriesInFlight int `json:"maxDeliveriesInFlight,omitempty" env:"FUNCD_EVENTING_MAX_DELIVERIES_IN_FLIGHT" validate:"min=1"`
MaxInFlightPerTarget int `` /* 131-byte string literal not displayed */
MaxQueuedPerSensor int `json:"maxQueuedPerSensor,omitempty" env:"FUNCD_EVENTING_MAX_QUEUED_PER_SENSOR" validate:"min=1"`
Deadletter struct {
Retention string `json:"retention,omitempty" env:"FUNCD_EVENTING_DEADLETTER_RETENTION"`
MaxEntries int `json:"maxEntries,omitempty" env:"FUNCD_EVENTING_DEADLETTER_MAX_ENTRIES" validate:"min=0"`
// DataDir is the DLQ's Badger directory. Empty ⇒ derived as <Storage.DataDir>/deadletter in
// Load(); ignored (in-memory) when Storage.Mode is memory. An explicit value overrides it.
DataDir string `json:"dataDir,omitempty" env:"FUNCD_EVENTING_DEADLETTER_DATA_DIR"`
} `json:"deadletter,omitempty"`
// BlobPollInterval is the cadence a `blob:` EventSource's prefixes are List-polled for new objects
// (ADR-0119, F83). A Go duration ("15s"); one cadence for all blob sources in V1.
BlobPollInterval string `json:"blobPollInterval,omitempty" env:"FUNCD_EVENTING_BLOB_POLL_INTERVAL"`
} `json:"eventing,omitempty"`
// Invoke tunes function invocation. MaxNestedInFlight caps the nested (fn-to-fn) calls in flight to one
// Function (ADR-0147); 0 ⇒ the default (10). It cannot be disabled. DefaultTimeout bounds external invokes
// only (ADR-0151): how long one waits for its response to start when the Function sets no spec.timeout; a
// link keeps links[].timeout (30 s default). A Go duration; empty or 0 ⇒ 60s, at most 1h.
Invoke struct {
MaxNestedInFlight int `json:"maxNestedInFlight,omitempty" env:"FUNCD_INVOKE_MAX_NESTED_IN_FLIGHT" validate:"min=0"`
DefaultTimeout string `json:"defaultTimeout,omitempty" env:"FUNCD_INVOKE_DEFAULT_TIMEOUT"`
} `json:"invoke,omitempty"`
// Site tunes the declarative static web app reconciler (ADR-0139, FEAT-0003/F103). DefaultIndex is
// the document a Site serves for "/" — and asserts present before it reports Ready — when its
// spec.index is empty. A relative path (no leading '/').
Site struct {
DefaultIndex string `json:"defaultIndex,omitempty" env:"FUNCD_SITE_DEFAULT_INDEX" validate:"omitempty,startsnotwith=/"`
} `json:"site,omitempty"`
}
Config is the funcd daemon config — the single source of truth (ADR-0062). Each field's tags declare its yaml key (`json`), its FUNCD_* overlay var (`env`), and its validation rule (`validate`).
func Load ¶
Load builds the effective Config (ADR-0062): defaults() → strict-decode the file at path ("" ⇒ skip) → overlay env (caarlos0/env; an unset FUNCD_* var leaves the field untouched) → apply the --memory flag → make storage.dataDir absolute → derive the dataDir-relative defaults → Validate. Any read/parse/unknown-key/enum error ⇒ fault.Invalid. The returned Config is fully populated + valid.
func (Config) Validate ¶
Validate runs the struct's `validate` tags (go-playground/validator) over the merged values, returning a fault.Invalid naming the offending yaml key + the allowed set. Load calls it on the effective config (so a bad value from the file, a FUNCD_* env, or the flag is all caught — the ADR-0061 env edge is closed); it is exported so a caller can validate a hand-built Config.
type Credential ¶ added in v0.4.0
type Credential struct {
TokenFile string `json:"tokenFile" validate:"required"`
Role string `json:"role" validate:"oneof=admin developer viewer"`
Namespaces []string `json:"namespaces,omitempty"`
}
Credential is one auth.credentials entry: the token lives in TokenFile, never in the config.
type CredentialList ¶ added in v0.4.0
type CredentialList []Credential
CredentialList is auth.credentials (ADR-0171): nil means the key is absent. UnmarshalJSON makes a present key non-nil, null included, and refuses an unknown entry key, which yaml.UnmarshalStrict does not check below a custom unmarshaler.
func (*CredentialList) UnmarshalJSON ¶ added in v0.4.0
func (l *CredentialList) UnmarshalJSON(b []byte) error
UnmarshalJSON decodes the list strictly, naming the entry index in an error.