package main
import (
"context"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"fmt"
"net"
"net/http"
"net/url"
"time"
"github.com/gorilla/mux"
"github.com/lxc/incus/v6/client"
internalInstance "github.com/lxc/incus/v6/internal/instance"
"github.com/lxc/incus/v6/internal/server/auth"
"github.com/lxc/incus/v6/internal/server/certificate"
"github.com/lxc/incus/v6/internal/server/cluster"
clusterRequest "github.com/lxc/incus/v6/internal/server/cluster/request"
"github.com/lxc/incus/v6/internal/server/db"
dbCluster "github.com/lxc/incus/v6/internal/server/db/cluster"
"github.com/lxc/incus/v6/internal/server/db/operationtype"
"github.com/lxc/incus/v6/internal/server/lifecycle"
"github.com/lxc/incus/v6/internal/server/operations"
"github.com/lxc/incus/v6/internal/server/request"
"github.com/lxc/incus/v6/internal/server/response"
"github.com/lxc/incus/v6/internal/server/state"
localUtil "github.com/lxc/incus/v6/internal/server/util"
internalUtil "github.com/lxc/incus/v6/internal/util"
"github.com/lxc/incus/v6/internal/version"
"github.com/lxc/incus/v6/shared/api"
"github.com/lxc/incus/v6/shared/logger"
localtls "github.com/lxc/incus/v6/shared/tls"
)
var certificatesCmd = APIEndpoint{
Path: "certificates",
Get: APIEndpointAction{Handler: certificatesGet, AccessHandler: allowAuthenticated},
Post: APIEndpointAction{Handler: certificatesPost, AllowUntrusted: true},
}
var certificateCmd = APIEndpoint{
Path: "certificates/{fingerprint}",
Delete: APIEndpointAction{Handler: certificateDelete, AccessHandler: allowAuthenticated},
Get: APIEndpointAction{Handler: certificateGet, AccessHandler: allowPermission(auth.ObjectTypeCertificate, auth.EntitlementCanView, "fingerprint")},
Patch: APIEndpointAction{Handler: certificatePatch, AccessHandler: allowAuthenticated},
Put: APIEndpointAction{Handler: certificatePut, AccessHandler: allowAuthenticated},
}
func certificatesGet(d *Daemon, r *http.Request) response.Response {
recursion := localUtil.IsRecursionRequest(r)
s := d.State()
userHasPermission, err := s.Authorizer.GetPermissionChecker(r.Context(), r, auth.EntitlementCanView, auth.ObjectTypeCertificate)
if err != nil {
return response.SmartError(err)
}
if recursion {
var certResponses []api.Certificate
var baseCerts []dbCluster.Certificate
var err error
err = d.State().DB.Cluster.Transaction(r.Context(), func(ctx context.Context, tx *db.ClusterTx) error {
baseCerts, err = dbCluster.GetCertificates(ctx, tx.Tx())
if err != nil {
return err
}
certResponses = make([]api.Certificate, 0, len(baseCerts))
for _, baseCert := range baseCerts {
if !userHasPermission(auth.ObjectCertificate(baseCert.Fingerprint)) {
continue
}
apiCert, err := baseCert.ToAPI(ctx, tx.Tx())
if err != nil {
return err
}
certResponses = append(certResponses, *apiCert)
}
return nil
})
if err != nil {
return response.SmartError(err)
}
return response.SyncResponse(true, certResponses)
}
body := []string{}
trustedCertificates := d.getTrustedCertificates()
for _, certs := range trustedCertificates {
for _, cert := range certs {
fingerprint := localtls.CertFingerprint(&cert)
if !userHasPermission(auth.ObjectCertificate(fingerprint)) {
continue
}
certificateURL := fmt.Sprintf("/%s/certificates/%s", version.APIVersion, fingerprint)
body = append(body, certificateURL)
}
}
return response.SyncResponse(true, body)
}
func updateCertificateCache(d *Daemon) {
s := d.State()
logger.Debug("Refreshing trusted certificate cache")
newCerts := map[certificate.Type]map[string]x509.Certificate{}
newProjects := map[string][]string{}
var certs []*api.Certificate
var dbCerts []dbCluster.Certificate
var localCerts []dbCluster.Certificate
var err error
err = s.DB.Cluster.Transaction(s.ShutdownCtx, func(ctx context.Context, tx *db.ClusterTx) error {
dbCerts, err = dbCluster.GetCertificates(ctx, tx.Tx())
if err != nil {
return err
}
certs = make([]*api.Certificate, len(dbCerts))
for i, c := range dbCerts {
certs[i], err = c.ToAPI(ctx, tx.Tx())
if err != nil {
return err
}
}
return nil
})
if err != nil {
logger.Warn("Failed reading certificates from global database", logger.Ctx{"err": err})
return
}
for i, dbCert := range dbCerts {
_, found := newCerts[dbCert.Type]
if !found {
newCerts[dbCert.Type] = make(map[string]x509.Certificate)
}
certBlock, _ := pem.Decode([]byte(dbCert.Certificate))
if certBlock == nil {
logger.Warn("Failed decoding certificate", logger.Ctx{"name": dbCert.Name, "err": err})
continue
}
cert, err := x509.ParseCertificate(certBlock.Bytes)
if err != nil {
logger.Warn("Failed parsing certificate", logger.Ctx{"name": dbCert.Name, "err": err})
continue
}
newCerts[dbCert.Type][localtls.CertFingerprint(cert)] = *cert
if dbCert.Restricted {
newProjects[localtls.CertFingerprint(cert)] = certs[i].Projects
}
if dbCert.Type == certificate.TypeServer {
localCerts = append(localCerts, dbCert)
}
}
err = s.DB.Node.Transaction(s.ShutdownCtx, func(ctx context.Context, tx *db.NodeTx) error {
return tx.ReplaceCertificates(localCerts)
})
if err != nil {
logger.Warn("Failed writing certificates to local database", logger.Ctx{"err": err})
}
d.clientCerts.SetCertificatesAndProjects(newCerts, newProjects)
}
func updateCertificateCacheFromLocal(d *Daemon) error {
s := d.State()
logger.Debug("Refreshing local trusted certificate cache")
newCerts := map[certificate.Type]map[string]x509.Certificate{}
var dbCerts []dbCluster.Certificate
var err error
err = s.DB.Node.Transaction(s.ShutdownCtx, func(ctx context.Context, tx *db.NodeTx) error {
dbCerts, err = tx.GetCertificates(ctx)
return err
})
if err != nil {
return fmt.Errorf("Failed reading certificates from local database: %w", err)
}
for _, dbCert := range dbCerts {
_, found := newCerts[dbCert.Type]
if !found {
newCerts[dbCert.Type] = make(map[string]x509.Certificate)
}
certBlock, _ := pem.Decode([]byte(dbCert.Certificate))
if certBlock == nil {
logger.Warn("Failed decoding certificate", logger.Ctx{"name": dbCert.Name, "err": err})
continue
}
cert, err := x509.ParseCertificate(certBlock.Bytes)
if err != nil {
logger.Warn("Failed parsing certificate", logger.Ctx{"name": dbCert.Name, "err": err})
continue
}
newCerts[dbCert.Type][localtls.CertFingerprint(cert)] = *cert
}
d.clientCerts.SetCertificates(newCerts)
return nil
}
func clusterMemberJoinTokenValid(s *state.State, r *http.Request, projectName string, joinToken *api.ClusterMemberJoinToken) (*api.Operation, error) {
ops, err := operationsGetByType(s, r, projectName, operationtype.ClusterJoinToken)
if err != nil {
return nil, fmt.Errorf("Failed getting cluster join token operations: %w", err)
}
var foundOp *api.Operation
for _, op := range ops {
if op.StatusCode != api.Running {
continue
}
if op.Resources == nil {
continue
}
opSecret, ok := op.Metadata["secret"]
if !ok {
continue
}
opServerName, ok := op.Metadata["serverName"]
if !ok {
continue
}
if opServerName == joinToken.ServerName && opSecret == joinToken.Secret {
foundOp = op
break
}
}
if foundOp != nil {
err = operationCancel(s, r, projectName, foundOp)
if err != nil {
return nil, fmt.Errorf("Failed to cancel operation %q: %w", foundOp.ID, err)
}
expiresAt, ok := foundOp.Metadata["expiresAt"]
if ok {
var expiry time.Time
if s.ServerName == foundOp.Location {
expiry, _ = expiresAt.(time.Time)
} else {
expiry, _ = time.Parse(time.RFC3339Nano, expiresAt.(string))
}
if time.Now().After(expiry) {
return nil, api.StatusErrorf(http.StatusForbidden, "Token has expired")
}
}
return foundOp, nil
}
return nil, nil
}
func certificateTokenValid(s *state.State, r *http.Request, addToken *api.CertificateAddToken) (*api.Operation, error) {
ops, err := operationsGetByType(s, r, api.ProjectDefaultName, operationtype.CertificateAddToken)
if err != nil {
return nil, fmt.Errorf("Failed getting certificate token operations: %w", err)
}
var foundOp *api.Operation
for _, op := range ops {
if op.StatusCode != api.Running {
continue
}
opSecret, ok := op.Metadata["secret"]
if !ok {
continue
}
if opSecret == addToken.Secret {
foundOp = op
break
}
}
if foundOp != nil {
err = operationCancel(s, r, api.ProjectDefaultName, foundOp)
if err != nil {
return nil, fmt.Errorf("Failed to cancel operation %q: %w", foundOp.ID, err)
}
expiresAt, ok := foundOp.Metadata["expiresAt"]
if ok {
expiry, _ := expiresAt.(time.Time)
if time.Now().After(expiry) {
return nil, api.StatusErrorf(http.StatusForbidden, "Token has expired")
}
}
return foundOp, nil
}
return nil, nil
}
func certificatesPost(d *Daemon, r *http.Request) response.Response {
s := d.State()
req := api.CertificatesPost{}
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
return response.BadRequest(err)
}
localHTTPSAddress := s.LocalConfig.HTTPSAddress()
if req.Token && req.Certificate != "" {
return response.BadRequest(fmt.Errorf("Can't use certificate if token is requested"))
}
if req.Token {
if req.Type != "client" {
return response.BadRequest(fmt.Errorf("Tokens can only be issued for client certificates"))
}
if localHTTPSAddress == "" {
return response.BadRequest(fmt.Errorf("Can't issue token when server isn't listening on network"))
}
}
trusted, _, _, err := d.Authenticate(nil, r)
if err != nil {
return response.SmartError(err)
}
if trusted && req.Certificate == "" && !req.Token {
return response.BadRequest(fmt.Errorf("Client is already trusted"))
}
var userCanCreateCertificates bool
err = s.Authorizer.CheckPermission(r.Context(), r, auth.ObjectServer(), auth.EntitlementCanCreateCertificates)
if err == nil {
userCanCreateCertificates = true
} else if !api.StatusErrorCheck(err, http.StatusForbidden) {
return response.SmartError(err)
}
if !trusted || !userCanCreateCertificates {
if req.Token {
return response.Forbidden(nil)
}
if req.TrustToken == "" {
return response.Forbidden(nil)
}
joinToken, err := internalUtil.JoinTokenDecode(req.TrustToken)
if err == nil {
joinOp, err := clusterMemberJoinTokenValid(s, r, api.ProjectDefaultName, joinToken)
if err != nil {
return response.InternalError(fmt.Errorf("Failed during search for join token operation: %w", err))
}
if joinOp == nil {
return response.Forbidden(fmt.Errorf("No matching cluster join operation found"))
}
} else {
joinToken, err := localtls.CertificateTokenDecode(req.TrustToken)
if err == nil {
joinOp, err := certificateTokenValid(s, r, joinToken)
if err != nil {
return response.InternalError(fmt.Errorf("Failed during search for certificate add token operation: %w", err))
}
if joinOp == nil {
return response.Forbidden(fmt.Errorf("No matching certificate add operation found"))
}
req = api.CertificatesPost{}
switch tokenReq := joinOp.Metadata["request"].(type) {
case api.CertificatesPost:
req.Name = tokenReq.Name
req.Type = tokenReq.Type
req.Restricted = tokenReq.Restricted
req.Projects = tokenReq.Projects
case map[string]any:
req.Name = tokenReq["name"].(string)
req.Type = tokenReq["type"].(string)
req.Restricted = tokenReq["restricted"].(bool)
for _, project := range tokenReq["projects"].([]any) {
req.Projects = append(req.Projects, project.(string))
}
default:
return response.InternalError(fmt.Errorf("Bad certificate add operation data"))
}
} else {
return response.Forbidden(nil)
}
}
}
dbReqType, err := certificate.FromAPIType(req.Type)
if err != nil {
return response.BadRequest(err)
}
var cert *x509.Certificate
if req.Certificate != "" {
data, err := base64.StdEncoding.DecodeString(req.Certificate)
if err != nil {
return response.BadRequest(err)
}
cert, err = x509.ParseCertificate(data)
if err != nil {
return response.BadRequest(fmt.Errorf("Invalid certificate material: %w", err))
}
} else if req.Token {
addresses, err := localUtil.ListenAddresses(localHTTPSAddress)
if err != nil {
return response.InternalError(err)
}
joinSecret, err := internalUtil.RandomHexString(32)
if err != nil {
return response.InternalError(err)
}
fingerprint, err := localtls.CertFingerprintStr(string(s.Endpoints.NetworkPublicKey()))
if err != nil {
return response.InternalError(err)
}
if req.Projects == nil {
req.Projects = []string{}
}
meta := map[string]any{
"secret": joinSecret,
"fingerprint": fingerprint,
"addresses": addresses,
"request": req,
}
expiry := s.GlobalConfig.RemoteTokenExpiry()
if expiry != "" {
expiresAt, err := internalInstance.GetExpiry(time.Now(), expiry)
if err != nil {
return response.InternalError(err)
}
meta["expiresAt"] = expiresAt
}
op, err := operations.OperationCreate(s, api.ProjectDefaultName, operations.OperationClassToken, operationtype.CertificateAddToken, nil, meta, nil, nil, nil, r)
if err != nil {
return response.InternalError(err)
}
return operations.OperationResponse(op)
} else if r.TLS != nil {
if len(r.TLS.PeerCertificates) < 1 {
return response.BadRequest(fmt.Errorf("No client certificate provided"))
}
cert = r.TLS.PeerCertificates[len(r.TLS.PeerCertificates)-1]
} else {
return response.BadRequest(fmt.Errorf("Can't use TLS data on non-TLS link"))
}
err = certificateValidate(cert)
if err != nil {
return response.BadRequest(err)
}
fingerprint := localtls.CertFingerprint(cert)
name := req.Name
if name == "" {
name = cert.Subject.CommonName
if name == "" {
remoteHost, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
return response.InternalError(err)
}
name = remoteHost
}
}
if !isClusterNotification(r) {
err := s.DB.Cluster.Transaction(r.Context(), func(ctx context.Context, tx *db.ClusterTx) error {
existingCert, _ := dbCluster.GetCertificateByFingerprintPrefix(ctx, tx.Tx(), fingerprint)
if existingCert != nil {
return api.StatusErrorf(http.StatusConflict, "Certificate already in trust store")
}
dbCert := dbCluster.Certificate{
Fingerprint: localtls.CertFingerprint(cert),
Type: dbReqType,
Name: name,
Certificate: string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})),
Restricted: req.Restricted,
Description: req.Description,
}
_, err := dbCluster.CreateCertificateWithProjects(ctx, tx.Tx(), dbCert, req.Projects)
return err
})
if err != nil {
return response.SmartError(err)
}
notifier, err := cluster.NewNotifier(s, s.Endpoints.NetworkCert(), s.ServerCert(), cluster.NotifyAlive)
if err != nil {
return response.SmartError(err)
}
req := api.CertificatesPost{
CertificatePut: api.CertificatePut{
Certificate: base64.StdEncoding.EncodeToString(cert.Raw),
Name: name,
Type: api.CertificateTypeClient,
},
}
err = notifier(func(client incus.InstanceServer) error {
return client.CreateCertificate(req)
})
if err != nil {
return response.SmartError(err)
}
err = s.Authorizer.AddCertificate(r.Context(), fingerprint)
if err != nil {
logger.Error("Failed to add certificate to authorizer", logger.Ctx{"fingerprint": fingerprint, "error": err})
}
}
s.UpdateCertificateCache()
lc := lifecycle.CertificateCreated.Event(fingerprint, request.CreateRequestor(r), nil)
s.Events.SendLifecycle(api.ProjectDefaultName, lc)
return response.SyncResponseLocation(true, nil, lc.Source)
}
func certificateGet(d *Daemon, r *http.Request) response.Response {
fingerprint, err := url.PathUnescape(mux.Vars(r)["fingerprint"])
if err != nil {
return response.SmartError(err)
}
var cert *api.Certificate
err = d.State().DB.Cluster.Transaction(r.Context(), func(ctx context.Context, tx *db.ClusterTx) error {
dbCertInfo, err := dbCluster.GetCertificateByFingerprintPrefix(ctx, tx.Tx(), fingerprint)
if err != nil {
return err
}
cert, err = dbCertInfo.ToAPI(ctx, tx.Tx())
return err
})
if err != nil {
return response.SmartError(err)
}
return response.SyncResponseETag(true, cert, cert)
}
func certificatePut(d *Daemon, r *http.Request) response.Response {
fingerprint, err := url.PathUnescape(mux.Vars(r)["fingerprint"])
if err != nil {
return response.SmartError(err)
}
var apiEntry *api.Certificate
err = d.State().DB.Cluster.Transaction(r.Context(), func(ctx context.Context, tx *db.ClusterTx) error {
oldEntry, err := dbCluster.GetCertificateByFingerprintPrefix(ctx, tx.Tx(), fingerprint)
if err != nil {
return err
}
apiEntry, err = oldEntry.ToAPI(ctx, tx.Tx())
return err
})
if err != nil {
return response.SmartError(err)
}
err = localUtil.EtagCheck(r, apiEntry)
if err != nil {
return response.PreconditionFailed(err)
}
req := api.CertificatePut{}
err = json.NewDecoder(r.Body).Decode(&req)
if err != nil {
return response.BadRequest(err)
}
clientType := clusterRequest.UserAgentClientType(r.Header.Get("User-Agent"))
return doCertificateUpdate(d, *apiEntry, req, clientType, r)
}
func certificatePatch(d *Daemon, r *http.Request) response.Response {
fingerprint, err := url.PathUnescape(mux.Vars(r)["fingerprint"])
if err != nil {
return response.SmartError(err)
}
var apiEntry *api.Certificate
err = d.State().DB.Cluster.Transaction(r.Context(), func(ctx context.Context, tx *db.ClusterTx) error {
oldEntry, err := dbCluster.GetCertificateByFingerprintPrefix(ctx, tx.Tx(), fingerprint)
if err != nil {
return err
}
apiEntry, err = oldEntry.ToAPI(ctx, tx.Tx())
return err
})
if err != nil {
return response.SmartError(err)
}
err = localUtil.EtagCheck(r, apiEntry)
if err != nil {
return response.PreconditionFailed(err)
}
req := *apiEntry
err = json.NewDecoder(r.Body).Decode(&req)
if err != nil {
return response.BadRequest(err)
}
clientType := clusterRequest.UserAgentClientType(r.Header.Get("User-Agent"))
return doCertificateUpdate(d, *apiEntry, req.Writable(), clientType, r)
}
func doCertificateUpdate(d *Daemon, dbInfo api.Certificate, req api.CertificatePut, clientType clusterRequest.ClientType, r *http.Request) response.Response {
s := d.State()
if clientType == clusterRequest.ClientTypeNormal {
reqDBType, err := certificate.FromAPIType(req.Type)
if err != nil {
return response.BadRequest(err)
}
dbCert := dbCluster.Certificate{
Certificate: dbInfo.Certificate,
Fingerprint: dbInfo.Fingerprint,
Restricted: req.Restricted,
Name: req.Name,
Type: reqDBType,
Description: req.Description,
}
var userCanEditCertificate bool
err = s.Authorizer.CheckPermission(r.Context(), r, auth.ObjectCertificate(dbInfo.Fingerprint), auth.EntitlementCanEdit)
if err == nil {
userCanEditCertificate = true
} else if !api.StatusErrorCheck(err, http.StatusForbidden) {
return response.SmartError(err)
}
certProjects := req.Projects
if !userCanEditCertificate {
if r.TLS == nil {
response.Forbidden(fmt.Errorf("Cannot update certificate information"))
}
if dbInfo.Restricted != req.Restricted || dbInfo.Name != req.Name || len(dbInfo.Projects) != len(req.Projects) {
return response.Forbidden(fmt.Errorf("Only the certificate can be changed"))
}
for i := 0; i < len(dbInfo.Projects); i++ {
if dbInfo.Projects[i] != req.Projects[i] {
return response.Forbidden(fmt.Errorf("Only the certificate can be changed"))
}
}
dbCert = dbCluster.Certificate{
Certificate: dbInfo.Certificate,
Fingerprint: dbInfo.Fingerprint,
Restricted: dbInfo.Restricted,
Name: dbInfo.Name,
Type: reqDBType,
Description: req.Description,
}
certProjects = dbInfo.Projects
if req.Certificate != "" && dbInfo.Certificate != req.Certificate {
certBlock, _ := pem.Decode([]byte(dbInfo.Certificate))
oldCert, err := x509.ParseCertificate(certBlock.Bytes)
if err != nil {
return response.InternalError(err)
}
trustedCerts := map[string]x509.Certificate{
dbInfo.Name: *oldCert,
}
trusted := false
for _, i := range r.TLS.PeerCertificates {
trusted, _ = localUtil.CheckTrustState(*i, trustedCerts, s.Endpoints.NetworkCert(), false)
if trusted {
break
}
}
if !trusted {
return response.Forbidden(fmt.Errorf("Certificate cannot be changed"))
}
}
}
if req.Certificate != "" && dbInfo.Certificate != req.Certificate {
block, _ := pem.Decode([]byte(req.Certificate))
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return response.BadRequest(fmt.Errorf("Invalid certificate material: %w", err))
}
dbCert.Certificate = string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}))
dbCert.Fingerprint = localtls.CertFingerprint(cert)
err = certificateValidate(cert)
if err != nil {
return response.BadRequest(err)
}
}
err = s.DB.UpdateCertificate(context.Background(), dbInfo.Fingerprint, dbCert, certProjects)
if err != nil {
return response.SmartError(err)
}
notifier, err := cluster.NewNotifier(s, s.Endpoints.NetworkCert(), s.ServerCert(), cluster.NotifyAlive)
if err != nil {
return response.SmartError(err)
}
err = notifier(func(client incus.InstanceServer) error {
return client.UpdateCertificate(dbCert.Fingerprint, req, "")
})
if err != nil {
return response.SmartError(err)
}
}
s.UpdateCertificateCache()
s.Events.SendLifecycle(api.ProjectDefaultName, lifecycle.CertificateUpdated.Event(dbInfo.Fingerprint, request.CreateRequestor(r), nil))
return response.EmptySyncResponse
}
func certificateDelete(d *Daemon, r *http.Request) response.Response {
s := d.State()
fingerprint, err := url.PathUnescape(mux.Vars(r)["fingerprint"])
if err != nil {
return response.SmartError(err)
}
if !isClusterNotification(r) {
var certInfo *dbCluster.Certificate
err := s.DB.Cluster.Transaction(r.Context(), func(ctx context.Context, tx *db.ClusterTx) error {
var err error
certInfo, err = dbCluster.GetCertificateByFingerprintPrefix(ctx, tx.Tx(), fingerprint)
if err != nil {
return err
}
return nil
})
if err != nil {
return response.SmartError(err)
}
var userCanEditCertificate bool
err = s.Authorizer.CheckPermission(r.Context(), r, auth.ObjectCertificate(certInfo.Fingerprint), auth.EntitlementCanEdit)
if err == nil {
userCanEditCertificate = true
} else if api.StatusErrorCheck(err, http.StatusForbidden) {
return response.SmartError(err)
}
if !userCanEditCertificate {
if r.TLS == nil {
response.Forbidden(fmt.Errorf("Cannot delete certificate"))
}
certBlock, _ := pem.Decode([]byte(certInfo.Certificate))
cert, err := x509.ParseCertificate(certBlock.Bytes)
if err != nil {
return response.InternalError(err)
}
trustedCerts := map[string]x509.Certificate{
certInfo.Name: *cert,
}
trusted := false
for _, i := range r.TLS.PeerCertificates {
trusted, _ = localUtil.CheckTrustState(*i, trustedCerts, s.Endpoints.NetworkCert(), false)
if trusted {
break
}
}
if !trusted {
return response.Forbidden(fmt.Errorf("Certificate cannot be deleted"))
}
}
err = s.DB.Cluster.Transaction(r.Context(), func(ctx context.Context, tx *db.ClusterTx) error {
return dbCluster.DeleteCertificate(ctx, tx.Tx(), certInfo.Fingerprint)
})
if err != nil {
return response.SmartError(err)
}
notifier, err := cluster.NewNotifier(s, s.Endpoints.NetworkCert(), s.ServerCert(), cluster.NotifyAlive)
if err != nil {
return response.SmartError(err)
}
err = notifier(func(client incus.InstanceServer) error {
return client.DeleteCertificate(certInfo.Fingerprint)
})
if err != nil {
return response.SmartError(err)
}
err = s.Authorizer.DeleteCertificate(r.Context(), certInfo.Fingerprint)
if err != nil {
logger.Error("Failed to remove certificate from authorizer", logger.Ctx{"fingerprint": certInfo.Fingerprint, "error": err})
}
}
s.UpdateCertificateCache()
s.Events.SendLifecycle(api.ProjectDefaultName, lifecycle.CertificateDeleted.Event(fingerprint, request.CreateRequestor(r), nil))
return response.EmptySyncResponse
}
func certificateValidate(cert *x509.Certificate) error {
if time.Now().Before(cert.NotBefore) {
return fmt.Errorf("The provided certificate isn't valid yet")
}
if time.Now().After(cert.NotAfter) {
return fmt.Errorf("The provided certificate is expired")
}
if cert.PublicKeyAlgorithm == x509.RSA {
pubKey, ok := cert.PublicKey.(*rsa.PublicKey)
if !ok {
return fmt.Errorf("Unable to validate the RSA certificate")
}
if pubKey.Size()*8 < 2048 {
return fmt.Errorf("RSA key is too weak (minimum of 2048bit)")
}
}
return nil
}