feat: wire BIND and Kea roles into the agents, allow revoking agents in read-only mode
ci / Protobuf (push) Successful in 2m18s
ci / Go (lint, test, vuln) (push) Failing after 3m11s
ci / Shell and licence headers (push) Successful in 8s
ci / Web (lint, test, audit, build) (push) Successful in 1m14s
ci / Integration (real BIND and Kea) (push) Successful in 1m42s
ci / Release build (dry run) (push) Failing after 2m24s
ci / Development environment (compose) (push) Successful in 38s
ci / Protobuf (push) Successful in 2m18s
ci / Go (lint, test, vuln) (push) Failing after 3m11s
ci / Shell and licence headers (push) Successful in 8s
ci / Web (lint, test, audit, build) (push) Successful in 1m14s
ci / Integration (real BIND and Kea) (push) Successful in 1m42s
ci / Release build (dry run) (push) Failing after 2m24s
ci / Development environment (compose) (push) Successful in 38s
Revoking or deleting an agent and deleting enrollment tokens lock agents out, a security function that must stay available when the license is read-only. Translations for the new problem codes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
31fb8285eb
commit
56892631be
@@ -4,18 +4,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
"git.beugsiud.de/beug.k/DDI/agent/core"
|
||||
"git.beugsiud.de/beug.k/DDI/agent/role/dhcp"
|
||||
"git.beugsiud.de/beug.k/DDI/internal/brand"
|
||||
)
|
||||
|
||||
func main() {
|
||||
os.Exit(core.Main(brand.AgentDHCPBinary, core.KindDHCP, newRole, os.Args[1:], os.Stdout, os.Stderr))
|
||||
}
|
||||
|
||||
// newRole is a placeholder until the DHCP role package is wired in.
|
||||
func newRole(func(v any) error) (core.Role, error) {
|
||||
return nil, errors.New("DHCP role not available in this build")
|
||||
os.Exit(core.Main(brand.AgentDHCPBinary, core.KindDHCP, dhcp.New, os.Args[1:], os.Stdout, os.Stderr))
|
||||
}
|
||||
|
||||
@@ -4,18 +4,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
"git.beugsiud.de/beug.k/DDI/agent/core"
|
||||
"git.beugsiud.de/beug.k/DDI/agent/role/dns"
|
||||
"git.beugsiud.de/beug.k/DDI/internal/brand"
|
||||
)
|
||||
|
||||
func main() {
|
||||
os.Exit(core.Main(brand.AgentDNSBinary, core.KindDNS, newRole, os.Args[1:], os.Stdout, os.Stderr))
|
||||
}
|
||||
|
||||
// newRole is a placeholder until the DNS role package is wired in.
|
||||
func newRole(func(v any) error) (core.Role, error) {
|
||||
return nil, errors.New("DNS role not available in this build")
|
||||
os.Exit(core.Main(brand.AgentDNSBinary, core.KindDNS, dns.New, os.Args[1:], os.Stdout, os.Stderr))
|
||||
}
|
||||
|
||||
@@ -324,6 +324,12 @@ func TestRevisionsAndDeployments(t *testing.T) {
|
||||
if err := e.svc.AgentHeartbeat(ctx, dnsAgent, service.HeartbeatInput{AppliedRevision: 2, LastGoodRevision: 2}); err != nil {
|
||||
t.Errorf("heartbeat in read-only mode: %v", err)
|
||||
}
|
||||
// Locking an agent out stays possible.
|
||||
ag = service.AgentDTO{}
|
||||
admin.mustStatus(200, "POST", "/api/v1/agents/"+dnsAgent+"/revoke", nil).json(t, &ag)
|
||||
if ag.RevokedAt == nil {
|
||||
t.Errorf("not revoked in read-only mode: %+v", ag)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLeases(t *testing.T) {
|
||||
|
||||
@@ -62,11 +62,13 @@ func (a *API) Routes() []Route {
|
||||
// Agents and enrollment (ADR 0021).
|
||||
perm(get, "/api/v1/agents", auth.PermIPAMRead, false, a.listAgents),
|
||||
perm(get, "/api/v1/agents/{id}", auth.PermIPAMRead, false, a.getAgent),
|
||||
perm(del, "/api/v1/agents/{id}", auth.PermSettingsAdm, true, a.deleteAgent),
|
||||
perm(post, "/api/v1/agents/{id}/revoke", auth.PermSettingsAdm, true, a.revokeAgent),
|
||||
// Locking agents out is a security function and stays available in
|
||||
// license read-only mode, like deleting enrollment tokens.
|
||||
perm(del, "/api/v1/agents/{id}", auth.PermSettingsAdm, false, a.deleteAgent),
|
||||
perm(post, "/api/v1/agents/{id}/revoke", auth.PermSettingsAdm, false, a.revokeAgent),
|
||||
perm(get, "/api/v1/enrollment-tokens", auth.PermSettingsAdm, false, a.listEnrollmentTokens),
|
||||
perm(post, "/api/v1/enrollment-tokens", auth.PermSettingsAdm, true, a.createEnrollmentToken),
|
||||
perm(del, "/api/v1/enrollment-tokens/{id}", auth.PermSettingsAdm, true, a.deleteEnrollmentToken),
|
||||
perm(del, "/api/v1/enrollment-tokens/{id}", auth.PermSettingsAdm, false, a.deleteEnrollmentToken),
|
||||
|
||||
// Revisions, deployments and leases.
|
||||
perm(get, "/api/v1/revisions", auth.PermIPAMRead, false, a.listRevisions),
|
||||
|
||||
@@ -199,11 +199,10 @@ func (s *Service) loadAgentDTO(ctx context.Context, tx *store.Tx, id string) (Ag
|
||||
return s.agentDTO(a, desired, last), nil
|
||||
}
|
||||
|
||||
// RevokeAgent revokes the agent's certificate and closes its stream.
|
||||
// RevokeAgent revokes the agent's certificate and closes its stream. Like
|
||||
// DeleteAgent and DeleteEnrollmentToken it works in license read-only mode:
|
||||
// locking out an agent is a security function.
|
||||
func (s *Service) RevokeAgent(ctx context.Context, a *Actor, id string) (AgentDTO, error) {
|
||||
if err := s.requireWritable(); err != nil {
|
||||
return AgentDTO{}, err
|
||||
}
|
||||
if !ValidUUID(id) {
|
||||
return AgentDTO{}, ErrNotFound
|
||||
}
|
||||
@@ -237,9 +236,6 @@ func (s *Service) RevokeAgent(ctx context.Context, a *Actor, id string) (AgentDT
|
||||
// DeleteAgent removes an agent with its deployments and leases. Its
|
||||
// certificate becomes worthless with the row.
|
||||
func (s *Service) DeleteAgent(ctx context.Context, a *Actor, id string) error {
|
||||
if err := s.requireWritable(); err != nil {
|
||||
return err
|
||||
}
|
||||
if !ValidUUID(id) {
|
||||
return ErrNotFound
|
||||
}
|
||||
@@ -412,9 +408,6 @@ func (s *Service) ListEnrollmentTokens(ctx context.Context) ([]EnrollmentTokenDT
|
||||
|
||||
// DeleteEnrollmentToken removes a token; an unused token becomes invalid.
|
||||
func (s *Service) DeleteEnrollmentToken(ctx context.Context, a *Actor, id string) error {
|
||||
if err := s.requireWritable(); err != nil {
|
||||
return err
|
||||
}
|
||||
if !tokenIDPattern.MatchString(id) {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
@@ -395,7 +395,9 @@
|
||||
"filter_name_taken": "Ein Filter mit diesem Namen existiert bereits.",
|
||||
"dns_settings_missing": "Für DNS fehlen noch Primär-Nameserver, Hostmaster und Nameserver in den DNS/DHCP-Einstellungen.",
|
||||
"config_invalid": "Die Konfiguration ist ungültig ({{path}}: {{message}}).",
|
||||
"config_failed": "Die Konfiguration konnte nicht erzeugt werden: {{message}}"
|
||||
"config_failed": "Die Konfiguration konnte nicht erzeugt werden: {{message}}",
|
||||
"agent_api_unavailable": "Die Agent-Schnittstelle des Servers ist nicht eingerichtet.",
|
||||
"revision_limit": "Die höchste mögliche Revisionsnummer ist erreicht."
|
||||
},
|
||||
"problems": {
|
||||
"required": "Pflichtfeld.",
|
||||
@@ -466,7 +468,8 @@
|
||||
"invalid_hostmaster": "Ungültiger Hostmaster (z. B. hostmaster@example.com).",
|
||||
"invalid_ttl": "Wert zwischen 1 und 2147483647.",
|
||||
"invalid_interface": "Ungültiger Schnittstellenname.",
|
||||
"invalid_path": "Ungültiger Pfad (absolut, ohne „..“, nur A–Z, a–z, 0–9 und . _ + @ -)."
|
||||
"invalid_path": "Ungültiger Pfad (absolut, ohne „..“, nur A–Z, a–z, 0–9 und . _ + @ -).",
|
||||
"out_of_range": "Der Wert liegt außerhalb des erlaubten Bereichs."
|
||||
},
|
||||
"config": {
|
||||
"title": "Erzeugte Konfiguration",
|
||||
|
||||
@@ -395,7 +395,9 @@
|
||||
"filter_name_taken": "A filter with this name already exists.",
|
||||
"dns_settings_missing": "DNS still needs a primary name server, hostmaster and name servers in the DNS/DHCP settings.",
|
||||
"config_invalid": "The configuration is invalid ({{path}}: {{message}}).",
|
||||
"config_failed": "The configuration could not be generated: {{message}}"
|
||||
"config_failed": "The configuration could not be generated: {{message}}",
|
||||
"agent_api_unavailable": "The server's agent interface is not configured.",
|
||||
"revision_limit": "The highest possible revision number has been reached."
|
||||
},
|
||||
"problems": {
|
||||
"required": "Required.",
|
||||
@@ -466,7 +468,8 @@
|
||||
"invalid_hostmaster": "Invalid hostmaster (e.g. hostmaster@example.com).",
|
||||
"invalid_ttl": "A value between 1 and 2147483647.",
|
||||
"invalid_interface": "Invalid interface name.",
|
||||
"invalid_path": "Invalid path (absolute, no \"..\", only A-Z, a-z, 0-9 and . _ + @ -)."
|
||||
"invalid_path": "Invalid path (absolute, no \"..\", only A-Z, a-z, 0-9 and . _ + @ -).",
|
||||
"out_of_range": "The value is out of the allowed range."
|
||||
},
|
||||
"config": {
|
||||
"title": "Generated configuration",
|
||||
|
||||
Reference in New Issue
Block a user