Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 44 additions & 10 deletions api/agentmesh.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions api/agentmesh.proto
Original file line number Diff line number Diff line change
Expand Up @@ -667,6 +667,12 @@ message BootstrapTokenCreateRequest {
// because a node that can always recover holds a credential that never
// expires.
bool autonomous_recovery = 6;
// Copied onto every node the token enrolls: such a node is forgotten
// soon after it falls silent (the control plane's
// --ephemeral-node-retention, an hour by default) instead of being kept
// for --node-retention. For members that come and go: a fleet in a scale
// test, a probe, a CI job.
bool ephemeral = 7;
}

// BootstrapTokenCreateResponse is returned (201) when a token is minted.
Expand Down Expand Up @@ -694,6 +700,7 @@ message BootstrapToken {
// or exhausted usages. Unset means never revoked.
google.protobuf.Timestamp revoke_time = 9;
bool autonomous_recovery = 10;
bool ephemeral = 11;
}

message BootstrapTokenListResponse {
Expand Down Expand Up @@ -746,6 +753,9 @@ message EnrolledNode {
google.protobuf.Timestamp expire_time = 8;
bool banned = 9;
bool autonomous_recovery = 10;
// The node was enrolled by an ephemeral token and is forgotten soon after
// it falls silent.
bool ephemeral = 11;
}

// RouterLease is a router's current registration with the control plane.
Expand Down
100 changes: 53 additions & 47 deletions cmd/agentmesh-control-plane/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,31 +36,32 @@ import (
)

var (
bindAddress string
pprofAddr string
dbDriver string
dbDSN string
dbDSNPath string
oidcIssuer string
workloadIssuer string
oidcClientID string
allowedAudiencesFlag string
keyRotationInterval time.Duration
keyGracePeriod time.Duration
leaseDuration time.Duration
dhtReplication int
dhtReprovideInterval time.Duration
biscuitTTL time.Duration
oidcSessionTTL time.Duration
workloadSessionTTL time.Duration
nodeRetention time.Duration
meshReconnectInterval time.Duration
adminTokenPath string
stsIssuerURL string
insecureSkipTLSVerify bool
logLevel string
logFormat string
autoApproveEnrollment bool
bindAddress string
pprofAddr string
dbDriver string
dbDSN string
dbDSNPath string
oidcIssuer string
workloadIssuer string
oidcClientID string
allowedAudiencesFlag string
keyRotationInterval time.Duration
keyGracePeriod time.Duration
leaseDuration time.Duration
dhtReplication int
dhtReprovideInterval time.Duration
biscuitTTL time.Duration
oidcSessionTTL time.Duration
workloadSessionTTL time.Duration
nodeRetention time.Duration
ephemeralNodeRetention time.Duration
meshReconnectInterval time.Duration
adminTokenPath string
stsIssuerURL string
insecureSkipTLSVerify bool
logLevel string
logFormat string
autoApproveEnrollment bool
)

var logger = golog.Logger("agentmesh-control-plane-cli")
Expand Down Expand Up @@ -102,6 +103,9 @@ func main() {
if nodeRetention <= 0 {
logger.Fatalf("--node-retention must be positive, got %s: node records are never kept forever", nodeRetention)
}
if ephemeralNodeRetention <= 0 {
logger.Fatalf("--ephemeral-node-retention must be positive, got %s", ephemeralNodeRetention)
}

var auds []string
for _, aud := range strings.Split(allowedAudiencesFlag, ",") {
Expand All @@ -126,27 +130,28 @@ func main() {
}()

opts := controlplane.Options{
ListenAddr: bindAddress,
DriverName: dbDriver,
DataSourceName: dbDSN,
OIDCIssuer: oidcIssuer,
WorkloadIssuer: workloadIssuer,
OIDCClientID: oidcClientID,
AllowedAudiences: auds,
LeaseDuration: leaseDuration,
KeyRotationInterval: keyRotationInterval,
KeyGracePeriod: keyGracePeriod,
DHTReplication: dhtReplication,
DHTReprovideInterval: dhtReprovideInterval,
InsecureSkipTLSVerify: insecureSkipTLSVerify,
BiscuitTimeout: 10 * time.Second,
BiscuitTTL: biscuitTTL,
OIDCSessionTTL: oidcSessionTTL,
WorkloadSessionTTL: workloadSessionTTL,
NodeRetention: nodeRetention,
AdminToken: adminToken,
AutoApproveEnrollment: autoApproveEnrollment,
STSIssuerURL: stsIssuerURL,
ListenAddr: bindAddress,
DriverName: dbDriver,
DataSourceName: dbDSN,
OIDCIssuer: oidcIssuer,
WorkloadIssuer: workloadIssuer,
OIDCClientID: oidcClientID,
AllowedAudiences: auds,
LeaseDuration: leaseDuration,
KeyRotationInterval: keyRotationInterval,
KeyGracePeriod: keyGracePeriod,
DHTReplication: dhtReplication,
DHTReprovideInterval: dhtReprovideInterval,
InsecureSkipTLSVerify: insecureSkipTLSVerify,
BiscuitTimeout: 10 * time.Second,
BiscuitTTL: biscuitTTL,
OIDCSessionTTL: oidcSessionTTL,
WorkloadSessionTTL: workloadSessionTTL,
NodeRetention: nodeRetention,
EphemeralNodeRetention: ephemeralNodeRetention,
AdminToken: adminToken,
AutoApproveEnrollment: autoApproveEnrollment,
STSIssuerURL: stsIssuerURL,
}

srv, err := controlplane.NewServer(opts, store)
Expand Down Expand Up @@ -206,7 +211,8 @@ func main() {
rootCmd.Flags().DurationVar(&biscuitTTL, "biscuit-ttl", api.BiscuitTokenTTL, "Lifespan minted into every issued Biscuit's expiration fact. Capped to the OIDC token's own expiry when shorter.")
rootCmd.Flags().DurationVar(&oidcSessionTTL, "oidc-session-ttl", api.OIDCSessionTTL, "How long an OIDC enrollment stays refreshable before the identity must re-authenticate with the OIDC provider. Shorter values keep the provider authoritative for offboarding at the cost of more frequent interactive re-enrollment.")
rootCmd.Flags().DurationVar(&workloadSessionTTL, "workload-session-ttl", controlplane.DefaultWorkloadSessionTTL, "How long a workload-issuer enrollment stays refreshable without presenting a fresh platform JWT on /refresh.")
rootCmd.Flags().DurationVar(&nodeRetention, "node-retention", controlplane.DefaultNodeRetention, "How long an enrolled node's record is kept after its session expired before it is deleted. Banned nodes are always kept. Must be positive: records are never kept forever.")
rootCmd.Flags().DurationVar(&nodeRetention, "node-retention", controlplane.DefaultNodeRetention, "How long an enrolled node's record is kept after its session expired, or after its last refresh for a node enrolled with a bootstrap token, before it is deleted. Banned nodes are always kept. Must be positive: records are never kept forever.")
rootCmd.Flags().DurationVar(&ephemeralNodeRetention, "ephemeral-node-retention", controlplane.DefaultEphemeralNodeRetention, "--node-retention for a node enrolled with an ephemeral bootstrap token: how long after its last refresh its record is kept. For members that come and go: a fleet in a scale test, a probe, a CI job.")
rootCmd.Flags().DurationVar(&meshReconnectInterval, "mesh-reconnect-interval", controlplane.DefaultMeshReconnectInterval, "How often the event publisher re-reads the router leases and dials any router it is not connected to.")
rootCmd.Flags().StringVar(&adminTokenPath, "admin-token-path", "", "Path to file containing the token for authenticating policy REST API requests (or env AGENTMESH_ADMIN_TOKEN)")
rootCmd.Flags().StringVar(&stsIssuerURL, "sts-issuer-url", "", "Canonical external URL of this control plane for OIDC/STS issuer and discovery metadata (or env AGENTMESH_STS_ISSUER_URL)")
Expand Down
3 changes: 2 additions & 1 deletion cmd/agentmesh-one/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,8 @@ func main() {
rootCmd.Flags().DurationVar(&cpTunables.KeyGracePeriod, "control-plane-key-grace-period", 0, "How long rotated-out keys stay valid for verification (0 keeps the component default)")
rootCmd.Flags().DurationVar(&cpTunables.BiscuitTTL, "control-plane-biscuit-ttl", 0, "Lifespan minted into issued biscuits (0 keeps the component default)")
rootCmd.Flags().DurationVar(&cpTunables.WorkloadSessionTTL, "control-plane-workload-session-ttl", 0, "How long a workload-issuer enrollment stays refreshable without presenting a fresh platform JWT on /refresh (0 keeps the component default)")
rootCmd.Flags().DurationVar(&cpTunables.NodeRetention, "control-plane-node-retention", 0, "How long a node record is kept after its session expired before it is deleted (0 keeps the component default)")
rootCmd.Flags().DurationVar(&cpTunables.NodeRetention, "control-plane-node-retention", 0, "How long a node record is kept after its session expired, or after its last refresh for a bootstrap enrollment, before it is deleted (0 keeps the component default)")
rootCmd.Flags().DurationVar(&cpTunables.EphemeralNodeRetention, "control-plane-ephemeral-node-retention", 0, "How long a node enrolled with an ephemeral bootstrap token is kept after its last refresh (0 keeps the component default)")
rootCmd.Flags().BoolVar(&cpTunables.ManualEnrollment, "control-plane-manual-enrollment", false, "Queue bootstrap enrollments for admin approval instead of auto-approving")

// Embedded router tunables.
Expand Down
6 changes: 6 additions & 0 deletions internal/console/public/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,7 @@ function renderBootstrapTokensTable(tokens) {
? `<span class="badge badge-rejected">Revoked</span>`
: `<span class="badge badge-approved">Active</span>`;
const recovery = token.autonomous_recovery ? 'Autonomous' : 'Manual';
const members = token.ephemeral ? 'Ephemeral' : 'Regular';
const action = revoked
? '-'
: `<button class="btn btn-sm btn-danger" onclick="revokeBootstrapToken('${escapeHTML(token.id)}')">Revoke</button>`;
Expand All @@ -789,6 +790,7 @@ function renderBootstrapTokensTable(tokens) {
<td><code>${escapeHTML(token.owner_id || '-')}</code></td>
<td>${escapeHTML(String(token.usages_count || 0))} / ${escapeHTML(String(token.max_usages || 0))}</td>
<td>${escapeHTML(expiresAt)}</td>
<td>${members}</td>
<td>${recovery}</td>
<td>${status}</td>
<td>${action}</td>
Expand All @@ -810,6 +812,7 @@ window.generateBootstrapToken = async function() {
const ttl_hours = parseInt(document.getElementById('token-ttl').value, 10) || 24;
const description = document.getElementById('token-desc').value;
const autonomous_recovery = document.getElementById('token-recovery').checked;
const ephemeral = document.getElementById('token-ephemeral').checked;

const payload = {
role,
Expand All @@ -824,6 +827,9 @@ window.generateBootstrapToken = async function() {
if (autonomous_recovery) {
payload.autonomous_recovery = true;
}
if (ephemeral) {
payload.ephemeral = true;
}

try {
const res = await actionRequest('api/user/bootstrap-tokens', 'POST', payload);
Expand Down
7 changes: 7 additions & 0 deletions internal/console/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,12 @@ <h3>Generate Bootstrap Token</h3>
<label for="token-desc" class="field-label">Description</label>
<input type="text" id="token-desc" class="field-input" placeholder="Token purpose">
</div>
<div class="input-group field">
<label for="token-ephemeral" class="field-label">
<input type="checkbox" id="token-ephemeral"> Ephemeral members
</label>
<small class="field-hint">Nodes enrolled with this token are forgotten an hour after they stop refreshing, instead of being kept for the node retention. For members that come and go: a fleet in a scale test, a probe, a CI job.</small>
</div>
<div class="input-group field" id="token-recovery-group">
<label for="token-recovery" class="field-label">
<input type="checkbox" id="token-recovery"> Autonomous recovery
Expand Down Expand Up @@ -387,6 +393,7 @@ <h3>Active Tokens</h3>
<th>Owner</th>
<th>Usages</th>
<th>Expires</th>
<th>Members</th>
<th>Recovery</th>
<th>Status</th>
<th>Actions</th>
Expand Down
2 changes: 2 additions & 0 deletions internal/controlplane/catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ func (s *Server) dropCatalogEntry(peerID string) {
}
s.catalogMu.Lock()
delete(s.catalog, pID.String())
catalogEntries.Set(float64(len(s.catalog)))
s.catalogMu.Unlock()
}

Expand Down Expand Up @@ -144,6 +145,7 @@ func (s *Server) HandleNodeCatalog(w http.ResponseWriter, r *http.Request) {
Services: req.Services,
ReportedAt: time.Now(),
}
catalogEntries.Set(float64(len(s.catalog)))
s.catalogMu.Unlock()

w.WriteHeader(http.StatusNoContent)
Expand Down
Loading
Loading