Repository navigation
DNM/SPLAT/OCPBUGS-86789: e2e-ccm-aws: investigate #tmp-ocpbugs-86789-early-requests #499
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
base: main
Are you sure you want to change the base?
Changes from 17 commits
dce0c27
14069d6
9f6750a
44f55af
2e06ea6
c3068f1
de7f1b1
013f9c2
e107f3e
d5fa8dc
bf37dd9
0b9194e
21bee92
0e80b33
16bde17
990a4cb
199f306
c962a18
db07955
e914c77
def241a
866f49d
cfbea3d
e9f603e
f858305
d0dacc7
3c979b8
bde13c5
a524105
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| FROM golang:1.22-alpine AS builder | ||
| WORKDIR /build | ||
| COPY main.go . | ||
| RUN go mod init healthserver && \ | ||
| go mod edit -go=1.22 && \ | ||
| CGO_ENABLED=0 go build -ldflags="-s -w" -o healthserver . | ||
|
|
||
| FROM scratch | ||
| COPY --from=builder /build/healthserver /healthserver | ||
| ENTRYPOINT ["/healthserver"] | ||
|
Comment on lines
+8
to
+10
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win Add a functional image health check. The final image has no As per path instructions: “HEALTHCHECK defined.” 🤖 Prompt for AI AgentsSource: Path instructions 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/sh
set -eu
file='openshift-tests/ccm-aws-tests/cmd/healthserver/Dockerfile'
printf '%s\n' '--- Dockerfile ---'
cat -n "$file"
printf '%s\n' '--- healthserver references ---'
rg -n --glob '!vendor' --glob '!node_modules' '8080|ListenAndServe|USER|healthserver' \
openshift-tests/ccm-aws-tests/cmd/healthserver \
openshift-tests/ccm-aws-tests 2>/dev/null | head -200
printf '%s\n' '--- final-stage instruction probe ---'
python3 - <<'PY'
from pathlib import Path
p = Path("openshift-tests/ccm-aws-tests/cmd/healthserver/Dockerfile")
lines = p.read_text().splitlines()
from_idx = [i for i, line in enumerate(lines) if line.lstrip().upper().startswith("FROM ")]
last = from_idx[-1]
stage = lines[last:]
print("final_stage_start_line:", last + 1)
print("final_stage_has_user:", any(line.lstrip().upper().startswith("USER ") for line in stage))
print("final_stage_user_lines:", [
(last + i + 1, line) for i, line in enumerate(stage)
if line.lstrip().upper().startswith("USER ")
])
print("final_stage_instructions:")
for i, line in enumerate(stage, last + 1):
stripped = line.strip()
if stripped and not stripped.startswith("#"):
print(f"{i}: {stripped}")
PYRepository: openshift/cluster-cloud-controller-manager-operator Length of output: 6360 Security Misconfiguration (CWE-250) Reachability: Internal Run The final 🤖 Prompt for AI AgentsSources: Path instructions, Linters/SAST tools |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,222 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "encoding/json" | ||
| "flag" | ||
| "fmt" | ||
| "log" | ||
| "net/http" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Record Line 94 records Proposed fix+ "net"
...
- s.mu.Lock()
- s.tcpUp = time.Now()
- s.mu.Unlock()
+ listener, err := net.Listen("tcp", srv.Addr)
+ if err != nil {
+ log.Fatalf("listen: %v", err)
+ }
+ s.mu.Lock()
+ s.tcpUp = time.Now()
+ s.mu.Unlock()
log.Printf("healthserver %s listening on :%d (startup-delay=%s)", id, *port, *startupDelay)
- if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
+ if err := srv.Serve(listener); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %v", err)
}Also applies to: 93-100 🤖 Prompt for AI Agents |
||
| "os" | ||
| "os/signal" | ||
| "sync" | ||
| "syscall" | ||
| "time" | ||
| ) | ||
|
|
||
| type serverState string | ||
|
|
||
| const ( | ||
| statePreReadyz serverState = "pre-readyz" | ||
| stateReady serverState = "ready" | ||
| stateDraining serverState = "draining" | ||
| stateShutdown serverState = "shutdown" | ||
| ) | ||
|
|
||
| type server struct { | ||
| mu sync.RWMutex | ||
| id string | ||
| state serverState | ||
| processStart time.Time | ||
| tcpUp time.Time | ||
| firstReadyz200 *time.Time | ||
| readyzFalseAt *time.Time | ||
| shutdownInitiated *time.Time | ||
| } | ||
|
|
||
| func main() { | ||
| port := flag.Int("port", 8080, "Service port") | ||
| startupDelay := flag.Duration("startup-delay", 30*time.Second, "Duration before /readyz returns 200") | ||
| flag.Parse() | ||
|
|
||
| id := os.Getenv("POD_NAME") | ||
| if id == "" { | ||
| id = fmt.Sprintf("healthserver-%d", os.Getpid()) | ||
| } | ||
|
|
||
| s := &server{ | ||
| id: id, | ||
| state: statePreReadyz, | ||
| processStart: time.Now(), | ||
| } | ||
|
|
||
| mux := http.NewServeMux() | ||
| mux.HandleFunc("GET /readyz", s.handleReadyz) | ||
| mux.HandleFunc("POST /admin/readyz", s.handleAdminReadyz) | ||
| mux.HandleFunc("POST /admin/shutdown", s.handleAdminShutdown) | ||
| mux.HandleFunc("GET /admin/lifecycle", s.handleAdminLifecycle) | ||
| mux.HandleFunc("/", s.handleMain) | ||
|
|
||
| srv := &http.Server{ | ||
| Addr: fmt.Sprintf(":%d", *port), | ||
| Handler: mux, | ||
| } | ||
|
Comment on lines
+58
to
+62
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- healthserver source ---'
sed -n '1,180p' openshift-tests/ccm-aws-tests/cmd/healthserver/main.go
printf '%s\n' '--- healthserver references and deployment/service definitions ---'
rg -n -C 4 'healthserver|ReadHeaderTimeout|ReadTimeout|WriteTimeout|IdleTimeout|ListenAndServe|targetPort|port:|LoadBalancer|healthCheck' \
openshift-tests/ccm-aws-tests/cmd openshift-tests/ccm-aws-tests/e2e/awsRepository: openshift/cluster-cloud-controller-manager-operator Length of output: 50409 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- remaining handlers ---'
sed -n '160,280p' openshift-tests/ccm-aws-tests/cmd/healthserver/main.go
printf '%s\n' '--- deployment and service builders ---'
rg -n -C 35 'func buildHealthserverDeployment|func buildHealthTransitionService' \
openshift-tests/ccm-aws-tests/e2e/aws
printf '%s\n' '--- exact health-transition annotations and ports ---'
python3 - <<'PY'
from pathlib import Path
p = Path("openshift-tests/ccm-aws-tests/e2e/aws/lb_health_transition.go")
lines = p.read_text().splitlines()
for i, line in enumerate(lines):
if "func buildHealthserverDeployment" in line or "func buildHealthTransitionService" in line:
lo = max(0, i - 2)
hi = min(len(lines), i + 130)
print(f"--- lines {lo+1}-{hi} ---")
for n in range(lo, hi):
print(f"{n+1}:{lines[n]}")
PYRepository: openshift/cluster-cloud-controller-manager-operator Length of output: 22100 Denial of Service (CWE-400): Uncontrolled Resource Consumption Reachability: External · Exploitability: Moderate Set bounded HTTP server timeouts. The health server is exposed through an external NLB. Without read deadlines, a client can keep connections open by sending request data slowly. Set 🧰 Tools🪛 ast-grep (0.45.0)[warning] 59-62: This (http-server-missing-read-timeout-go) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
|
|
||
| go func() { | ||
| log.Printf("startup delay: %s (readyz returns 503 until then)", *startupDelay) | ||
| time.Sleep(*startupDelay) | ||
| s.mu.Lock() | ||
| if s.state == statePreReadyz { | ||
| now := time.Now() | ||
| s.firstReadyz200 = &now | ||
| s.state = stateReady | ||
| log.Printf("startup delay elapsed, readyz now returns 200") | ||
| } | ||
| s.mu.Unlock() | ||
| }() | ||
|
|
||
| // On SIGTERM (sent by kubelet during pod deletion), transition to | ||
| // "draining" state: /readyz returns 503 but the server keeps serving | ||
| // on / with X-Server-State: draining. The pod continues to serve | ||
| // for terminationGracePeriodSeconds (set by the Deployment) before | ||
| // kubelet force-kills it. This matches KAS behavior during rollouts: | ||
| // SIGTERM → readyz→503 → keep serving for shutdown-delay-duration. | ||
| go func() { | ||
| ch := make(chan os.Signal, 1) | ||
| signal.Notify(ch, syscall.SIGTERM, syscall.SIGINT) | ||
| sig := <-ch | ||
| log.Printf("received %s, setting readyz→503 (draining), server continues serving", sig) | ||
| s.mu.Lock() | ||
| now := time.Now() | ||
| s.shutdownInitiated = &now | ||
| s.readyzFalseAt = &now | ||
| s.state = stateDraining | ||
| s.mu.Unlock() | ||
| // Do NOT call srv.Shutdown() — keep serving until kubelet kills us | ||
| // at the end of terminationGracePeriodSeconds. This simulates KAS | ||
| // keeping its TCP port open during shutdown-delay-duration. | ||
| }() | ||
|
|
||
| s.mu.Lock() | ||
| s.tcpUp = time.Now() | ||
| s.mu.Unlock() | ||
|
|
||
| log.Printf("healthserver %s listening on :%d (startup-delay=%s)", id, *port, *startupDelay) | ||
| if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { | ||
| log.Fatalf("listen: %v", err) | ||
| } | ||
| } | ||
|
|
||
| func (s *server) handleMain(w http.ResponseWriter, r *http.Request) { | ||
| s.mu.RLock() | ||
| state := s.state | ||
| start := s.processStart | ||
| var firstReadyz string | ||
| if s.firstReadyz200 != nil { | ||
| firstReadyz = s.firstReadyz200.Format(time.RFC3339Nano) | ||
| } else { | ||
| firstReadyz = "never" | ||
| } | ||
| s.mu.RUnlock() | ||
|
|
||
| w.Header().Set("X-Server-State", string(state)) | ||
| w.Header().Set("X-Server-ID", s.id) | ||
| w.Header().Set("X-Server-Start-Time", start.Format(time.RFC3339Nano)) | ||
| w.Header().Set("X-First-Readyz-Time", firstReadyz) | ||
| w.WriteHeader(http.StatusOK) | ||
| fmt.Fprintf(w, "server_id=%s state=%s\n", s.id, state) | ||
| } | ||
|
|
||
| func (s *server) handleReadyz(w http.ResponseWriter, r *http.Request) { | ||
| s.mu.RLock() | ||
| state := s.state | ||
| s.mu.RUnlock() | ||
|
|
||
| if state == stateReady { | ||
| w.WriteHeader(http.StatusOK) | ||
| fmt.Fprint(w, "ok") | ||
| } else { | ||
| w.WriteHeader(http.StatusServiceUnavailable) | ||
| fmt.Fprintf(w, "not ready: %s", state) | ||
| } | ||
| } | ||
|
|
||
| func (s *server) handleAdminReadyz(w http.ResponseWriter, r *http.Request) { | ||
| ready := r.URL.Query().Get("ready") | ||
| s.mu.Lock() | ||
| defer s.mu.Unlock() | ||
|
|
||
| switch ready { | ||
| case "true": | ||
| if s.firstReadyz200 == nil { | ||
| now := time.Now() | ||
| s.firstReadyz200 = &now | ||
| } | ||
| s.state = stateReady | ||
| fmt.Fprint(w, "readyz=true") | ||
| case "false": | ||
| now := time.Now() | ||
| s.readyzFalseAt = &now | ||
| s.state = stateDraining | ||
| fmt.Fprint(w, "readyz=false") | ||
| default: | ||
| http.Error(w, "ready must be 'true' or 'false'", http.StatusBadRequest) | ||
| } | ||
| } | ||
|
|
||
| func (s *server) handleAdminShutdown(w http.ResponseWriter, r *http.Request) { | ||
| delayStr := r.URL.Query().Get("delay") | ||
| delay := time.Duration(0) | ||
| if delayStr != "" { | ||
| d, err := time.ParseDuration(delayStr) | ||
| if err != nil { | ||
| http.Error(w, fmt.Sprintf("invalid delay: %v", err), http.StatusBadRequest) | ||
| return | ||
| } | ||
| delay = d | ||
| } | ||
|
|
||
| s.mu.Lock() | ||
| now := time.Now() | ||
| s.shutdownInitiated = &now | ||
| s.state = stateShutdown | ||
| s.mu.Unlock() | ||
|
|
||
| fmt.Fprintf(w, "shutdown initiated, delay=%s", delay) | ||
|
|
||
| go func() { | ||
| time.Sleep(delay) | ||
| os.Exit(0) | ||
| }() | ||
| } | ||
|
|
||
| func (s *server) handleAdminLifecycle(w http.ResponseWriter, r *http.Request) { | ||
| s.mu.RLock() | ||
| j := struct { | ||
| ServerID string `json:"server_id"` | ||
| ProcessStart string `json:"t_process_start"` | ||
| TCPUp string `json:"t_tcp_up"` | ||
| FirstReadyz200 *string `json:"t_first_readyz_200"` | ||
| ReadyzFalseAt *string `json:"t_readyz_false_at"` | ||
| ShutdownInitiated *string `json:"t_shutdown_initiated"` | ||
| }{ | ||
| ServerID: s.id, | ||
| ProcessStart: s.processStart.Format(time.RFC3339Nano), | ||
| TCPUp: s.tcpUp.Format(time.RFC3339Nano), | ||
| } | ||
| if s.firstReadyz200 != nil { | ||
| v := s.firstReadyz200.Format(time.RFC3339Nano) | ||
| j.FirstReadyz200 = &v | ||
| } | ||
| if s.readyzFalseAt != nil { | ||
| v := s.readyzFalseAt.Format(time.RFC3339Nano) | ||
| j.ReadyzFalseAt = &v | ||
| } | ||
| if s.shutdownInitiated != nil { | ||
| v := s.shutdownInitiated.Format(time.RFC3339Nano) | ||
| j.ShutdownInitiated = &v | ||
| } | ||
| s.mu.RUnlock() | ||
|
|
||
| w.Header().Set("Content-Type", "application/json") | ||
| json.NewEncoder(w).Encode(j) | ||
| } | ||
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.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: openshift/cluster-cloud-controller-manager-operator
Length of output: 643
Security Misconfiguration (CWE-494): Download of Code Without Integrity Check
Reachability: Internal
Pin the builder image by digest.
golang:1.22-alpineis a mutable non-Red-Hat tag. Use a reviewed, platform-appropriate digest.🧰 Tools
🪛 Trivy (0.72.0)
[error] 1-1: Image user should not be 'root'
Specify at least 1 USER command in Dockerfile with non-root user as argument
Rule: DS-0002
Learn more
(IaC/Dockerfile)
[error] 1-1: Image user should not be 'root'
Specify at least 1 USER command in Dockerfile with non-root user as argument
Rule: DS-0002
Learn more
(IaC/Dockerfile)
🤖 Prompt for AI Agents
Source: Path instructions