package main import ( "context" "encoding/json" "fmt" "io" "log" "net/http" "time" ) type CreatePolicyRequest struct { PolicyName string `json:"policyname"` PolicyDocument Policy `json:"policy"` } func CreatePolicy(w http.ResponseWriter, r *http.Request) { // Only allow POST method if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } // XXX Temporary Account Handler accountid := r.Header.Get("Account") if accountid == "" { http.Error(w, "Account header is required", http.StatusUnauthorized) return } // Optional: enforce content type if r.Header.Get("Content-Type") != "application/json" { http.Error(w, "Content-Type must be application/json", http.StatusUnsupportedMediaType) return } // Read body with size limit (protect against huge requests) const maxBodyBytes = 1 << 20 // 1 MB r.Body = http.MaxBytesReader(w, r.Body, maxBodyBytes) // Decode JSON into our struct var policydocumentrequest CreatePolicyRequest decoder := json.NewDecoder(r.Body) decoder.DisallowUnknownFields() // strict mode - reject unknown fields if err := decoder.Decode(&policydocumentrequest); err != nil { switch { case err == io.EOF: http.Error(w, "Empty request body", http.StatusBadRequest) case err.Error() == "http: request body too large": http.Error(w, "Request body too large (max 1MB)", http.StatusRequestEntityTooLarge) default: http.Error(w, fmt.Sprintf("Invalid JSON: %v", err), http.StatusBadRequest) } return } // Optional: basic validation if policydocumentrequest.PolicyDocument.Actions == nil || policydocumentrequest.PolicyDocument.Effect == "" || policydocumentrequest.PolicyDocument.Resources == nil { http.Error(w, "Missing required fields: id and title", http.StatusBadRequest) return } // Process the results log.Printf("New Policy: Account:%s, PolicyIdentifier=%s, Actions=%q, Effect=%q, Resources=%q, Comment=%q", accountid, policydocumentrequest.PolicyDocument.PolicyIdentifier, policydocumentrequest.PolicyDocument.Actions, policydocumentrequest.PolicyDocument.Effect, policydocumentrequest.PolicyDocument.Resources, policydocumentrequest.PolicyDocument.Comment) // Check if policy with the same name already exists for the account checkExisting := pool.QueryRow(context.Background(), "SELECT FROM policies WHERE accountid = $1 AND policyname = $2", accountid, policydocumentrequest.PolicyName) err := checkExisting.Scan() if err == nil { pri := "pri:iam::" + accountid + ":policy/" + policydocumentrequest.PolicyName response := map[string]interface{}{ "status": "fail", "pri": pri, "message": "Policy " + policydocumentrequest.PolicyName + " already exists: " + pri, "timestamp": fmt.Sprintf("%d", time.Now().Unix()), } json.NewEncoder(w).Encode(response) http.Error(w, "Policy with the same name already exists for this account", http.StatusConflict) return } _, err = pool.Exec(context.Background(), "INSERT INTO policies (accountid, policyname, document) VALUES($1, $2, $3) ON CONFLICT DO NOTHING", accountid, policydocumentrequest.PolicyName, policydocumentrequest.PolicyDocument) if err != nil { log.Printf("Error inserting policy: %v", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } // Success response w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) // arnservice:region:account-id:resource-type:resource-id pri := "pri:iam::" + accountid + ":policy/" + policydocumentrequest.PolicyName response := map[string]interface{}{ "status": "success", "pri": pri, "message": "Policy created", "timestamp": fmt.Sprintf("%d", time.Now().Unix()), } json.NewEncoder(w).Encode(response) }