-
Notifications
You must be signed in to change notification settings - Fork 0
Feat/api to provision cert #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
29 commits
Select commit
Hold shift + click to select a range
a3e31d5
Add cert service to create the CA cert
JckHoe 3d05fe7
Add domain name config
JckHoe 9da3412
Add function to GenerateAgentCert
JckHoe ace2945
Add the API handler for agent cert generate
JckHoe cf0a65f
Update main.go to init the cert service
JckHoe 01086ee
Add provision agent cert script sample
JckHoe 8f34201
change domain anmes and ip addresses config for tls provision to comm…
JckHoe 87d3a75
Add CA key file config
JckHoe 489d81d
bug fix on the CA key loading for the private key format
JckHoe 2fdfa85
refactor the cert layer
JckHoe 22c5f03
update GenerateServerCert signature
JckHoe 06bfe85
update the Generate CA function
JckHoe 94ad9f2
Clean up the cert KeyToPEM function to utils
JckHoe c7fe218
Add Clear all server certs API
JckHoe 57338e7
Add simple curl
JckHoe e3fa4f2
Add simple curl
JckHoe 6c1dfe9
simplify defer for zipwriter close
JckHoe 7759bbb
bug fix on zip writer for agent generate
JckHoe 0631367
Update domainnames config handling
JckHoe f08a6fd
Update API spec
JckHoe 40487e6
Update the test scripts
JckHoe 498f799
Update temp server cert clear
JckHoe 6334b13
Update temp endpoint
JckHoe f9b4f1a
Fix findings
JckHoe a91121a
cleanup delete endpoint and add admin api key
JckHoe dc5b0ee
Update test scripts
JckHoe 60a7854
Update error logging
JckHoe 3c9eab9
use defer.Close()
lwlee2608 d6c3a0b
revert
lwlee2608 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| Potential Issues | ||
|
|
||
| 1. Missing server certificate regeneration when domain/IP config changes | ||
| 2. No rate limiting on certificate generation endpoints | ||
| 3. Agent cert directory cleanup - DeleteAgentCert removes entire directory, which could be dangerous |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,234 @@ | ||
| package handler | ||
|
|
||
| import ( | ||
| "archive/zip" | ||
| "bytes" | ||
| "crypto/rsa" | ||
| "crypto/x509" | ||
| "fmt" | ||
| "log/slog" | ||
| "net/http" | ||
|
|
||
| "github.com/EternisAI/silo-proxy/internal/cert" | ||
| "github.com/gin-gonic/gin" | ||
| ) | ||
|
|
||
| func (h *CertHandler) validateAgentID(ctx *gin.Context) (string, bool) { | ||
| agentID := ctx.Param("id") | ||
| if err := cert.ValidateAgentID(agentID); err != nil { | ||
| slog.Warn("Invalid agent ID", "agent_id", agentID, "error", err) | ||
| ctx.JSON(http.StatusBadRequest, gin.H{ | ||
| "error": err.Error(), | ||
| }) | ||
| return "", false | ||
| } | ||
| return agentID, true | ||
| } | ||
|
|
||
| type CertHandler struct { | ||
| certService *cert.Service | ||
| } | ||
|
|
||
| func NewCertHandler(certService *cert.Service) *CertHandler { | ||
| return &CertHandler{ | ||
| certService: certService, | ||
| } | ||
| } | ||
|
|
||
| func (h *CertHandler) CreateAgentCertificate(ctx *gin.Context) { | ||
| if h.certService == nil { | ||
| slog.Warn("Agent cert creation requested but TLS is disabled") | ||
| ctx.JSON(http.StatusBadRequest, gin.H{ | ||
| "error": "TLS is not enabled on this server", | ||
| }) | ||
| return | ||
| } | ||
|
|
||
| agentID, ok := h.validateAgentID(ctx) | ||
| if !ok { | ||
| return | ||
| } | ||
|
|
||
| if h.certService.AgentCertExists(agentID) { | ||
| slog.Warn("Certificate already exists", "agent_id", agentID) | ||
| ctx.JSON(http.StatusConflict, gin.H{ | ||
| "error": "Certificate already exists for this agent", | ||
| }) | ||
| return | ||
| } | ||
|
|
||
| slog.Info("Creating agent certificate", "agent_id", agentID) | ||
|
|
||
| agentCert, agentKey, err := h.certService.GenerateAgentCert(agentID) | ||
| if err != nil { | ||
| slog.Error("Failed to generate agent certificate", "error", err, "agent_id", agentID) | ||
| ctx.JSON(http.StatusInternalServerError, gin.H{ | ||
| "error": "Failed to generate agent certificate", | ||
| }) | ||
| return | ||
| } | ||
|
|
||
| caCertBytes, err := h.certService.GetCACert() | ||
| if err != nil { | ||
| slog.Error("Failed to read CA certificate", "error", err, "agent_id", agentID) | ||
| ctx.JSON(http.StatusInternalServerError, gin.H{ | ||
| "error": "Failed to read CA certificate", | ||
| }) | ||
| return | ||
| } | ||
|
|
||
| zipBuffer, err := h.createCertZip(agentID, agentCert, agentKey, caCertBytes) | ||
| if err != nil { | ||
| slog.Error("Failed to create zip file", "error", err, "agent_id", agentID) | ||
| ctx.JSON(http.StatusInternalServerError, gin.H{ | ||
| "error": "Failed to create zip file", | ||
| }) | ||
| return | ||
| } | ||
|
|
||
| ctx.Header("Content-Type", "application/zip") | ||
| ctx.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s-certs.zip\"", agentID)) | ||
| ctx.Data(http.StatusCreated, "application/zip", zipBuffer.Bytes()) | ||
|
|
||
| slog.Info("Agent certificate created successfully", "agent_id", agentID, "zip_size", zipBuffer.Len()) | ||
| } | ||
|
|
||
| func (h *CertHandler) GetAgentCertificate(ctx *gin.Context) { | ||
| if h.certService == nil { | ||
| slog.Warn("Agent cert retrieval requested but TLS is disabled") | ||
| ctx.JSON(http.StatusBadRequest, gin.H{ | ||
| "error": "TLS is not enabled on this server", | ||
| }) | ||
| return | ||
| } | ||
|
|
||
| agentID, ok := h.validateAgentID(ctx) | ||
| if !ok { | ||
| return | ||
| } | ||
|
|
||
| if !h.certService.AgentCertExists(agentID) { | ||
| slog.Warn("Certificate not found", "agent_id", agentID) | ||
| ctx.JSON(http.StatusNotFound, gin.H{ | ||
| "error": "Certificate not found for this agent", | ||
| }) | ||
| return | ||
| } | ||
|
|
||
| slog.Info("Retrieving agent certificate", "agent_id", agentID) | ||
|
|
||
| agentCertBytes, agentKeyBytes, err := h.certService.GetAgentCert(agentID) | ||
| if err != nil { | ||
| slog.Error("Failed to read agent certificate", "error", err, "agent_id", agentID) | ||
| ctx.JSON(http.StatusInternalServerError, gin.H{ | ||
| "error": "Failed to read agent certificate", | ||
| }) | ||
| return | ||
| } | ||
|
|
||
| caCertBytes, err := h.certService.GetCACert() | ||
| if err != nil { | ||
| slog.Error("Failed to read CA certificate", "error", err, "agent_id", agentID) | ||
| ctx.JSON(http.StatusInternalServerError, gin.H{ | ||
| "error": "Failed to read CA certificate", | ||
| }) | ||
| return | ||
| } | ||
|
|
||
| zipBuffer, err := h.createCertZipFromBytes(agentID, agentCertBytes, agentKeyBytes, caCertBytes) | ||
| if err != nil { | ||
| slog.Error("Failed to create zip file", "error", err, "agent_id", agentID) | ||
| ctx.JSON(http.StatusInternalServerError, gin.H{ | ||
| "error": "Failed to create zip file", | ||
| }) | ||
| return | ||
| } | ||
|
|
||
| ctx.Header("Content-Type", "application/zip") | ||
| ctx.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s-certs.zip\"", agentID)) | ||
| ctx.Data(http.StatusOK, "application/zip", zipBuffer.Bytes()) | ||
|
|
||
| slog.Info("Agent certificate retrieved successfully", "agent_id", agentID, "zip_size", zipBuffer.Len()) | ||
| } | ||
|
|
||
| func (h *CertHandler) DeleteAgentCertificate(ctx *gin.Context) { | ||
| if h.certService == nil { | ||
| slog.Warn("Agent cert deletion requested but TLS is disabled") | ||
| ctx.JSON(http.StatusBadRequest, gin.H{ | ||
| "error": "TLS is not enabled on this server", | ||
| }) | ||
| return | ||
| } | ||
|
|
||
| agentID, ok := h.validateAgentID(ctx) | ||
| if !ok { | ||
| return | ||
| } | ||
|
|
||
| if !h.certService.AgentCertExists(agentID) { | ||
| slog.Warn("Certificate not found for deletion", "agent_id", agentID) | ||
| ctx.JSON(http.StatusNotFound, gin.H{ | ||
| "error": "Certificate not found for this agent", | ||
| }) | ||
| return | ||
| } | ||
|
|
||
| slog.Info("Deleting agent certificate", "agent_id", agentID) | ||
|
|
||
| certDir := h.certService.GetAgentCertDir(agentID) | ||
| if err := h.certService.DeleteAgentCert(agentID); err != nil { | ||
| slog.Error("Failed to delete agent certificate", "error", err, "agent_id", agentID) | ||
| ctx.JSON(http.StatusInternalServerError, gin.H{ | ||
| "error": "Failed to delete agent certificate", | ||
| }) | ||
| return | ||
| } | ||
|
|
||
| ctx.JSON(http.StatusOK, gin.H{ | ||
| "message": "Successfully deleted agent certificate", | ||
| "deleted_paths": []string{certDir}, | ||
| }) | ||
| slog.Info("Agent certificate deleted successfully", "agent_id", agentID) | ||
| } | ||
|
|
||
| func (h *CertHandler) createCertZip(agentID string, agentCert *x509.Certificate, agentKey *rsa.PrivateKey, caCertBytes []byte) (*bytes.Buffer, error) { | ||
| agentCertPEM, err := cert.CertToPEM(agentCert) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to encode agent certificate: %w", err) | ||
| } | ||
|
|
||
| agentKeyPEM, err := cert.KeyToPEM(agentKey) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to encode agent key: %w", err) | ||
| } | ||
|
|
||
| return h.createCertZipFromBytes(agentID, agentCertPEM, agentKeyPEM, caCertBytes) | ||
| } | ||
|
|
||
| func (h *CertHandler) createCertZipFromBytes(agentID string, certPEM, keyPEM, caCertBytes []byte) (*bytes.Buffer, error) { | ||
| zipBuffer := new(bytes.Buffer) | ||
| zipWriter := zip.NewWriter(zipBuffer) | ||
| defer zipWriter.Close() | ||
|
|
||
| files := map[string][]byte{ | ||
| fmt.Sprintf("%s-cert.pem", agentID): certPEM, | ||
| fmt.Sprintf("%s-key.pem", agentID): keyPEM, | ||
| "ca-cert.pem": caCertBytes, | ||
| } | ||
|
|
||
| for filename, content := range files { | ||
| f, err := zipWriter.Create(filename) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to create zip entry for %s: %w", filename, err) | ||
| } | ||
| if _, err := f.Write(content); err != nil { | ||
| return nil, fmt.Errorf("failed to write zip entry for %s: %w", filename, err) | ||
| } | ||
| } | ||
|
|
||
| if err := zipWriter.Close(); err != nil { | ||
| return nil, fmt.Errorf("failed to finalize zip archive: %w", err) | ||
| } | ||
|
|
||
| return zipBuffer, nil | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don’t hardcode an empty admin API key.
Passing
""means cert routes will always return 503 even if a key is configured. If the agent should support these endpoints, wire through the configured key; if not, consider explicitly disabling the routes.✅ Suggested wiring
📝 Committable suggestion
🤖 Prompt for AI Agents