#load "gateway.fsx" open System open System.IO open System.Net open System.Net.Http open System.Text open System.Text.Json open System.Threading open System.Threading.Tasks open Gateway let require condition message = if not condition then invalidOp message type VaultHandler() = inherit HttpMessageHandler() let seen = ResizeArray() member _.Seen = seen member private _.Respond(request: HttpRequestMessage) = let path = request.RequestUri.AbsolutePath let body = if path = "/v1/auth/kubernetes/login" then use document = JsonDocument.Parse(request.Content.ReadAsStream()) require (document.RootElement.GetProperty("role").GetString() = "bootstrap") "Vault role changed" require (document.RootElement.GetProperty("jwt").GetString() = "service-account-jwt") "Vault JWT changed" """{"auth":{"client_token":"vault-token"}}""" elif request.Method = HttpMethod.Get then let token = request.Headers.GetValues("X-Vault-Token") |> Seq.exactlyOne seen.Add(path, token) """{"data":{"data":{"token":"stored"}}}""" else let token = request.Headers.GetValues("X-Vault-Token") |> Seq.exactlyOne seen.Add(path, token) use document = JsonDocument.Parse(request.Content.ReadAsStream()) require (document.RootElement.GetProperty("data").GetProperty("token").GetString() = "issued") "Vault write did not use a KVv2 envelope" "{}" let response = new HttpResponseMessage(HttpStatusCode.OK) response.Content <- new StringContent(body, Encoding.UTF8, "application/json") response override this.Send(request: HttpRequestMessage, _: CancellationToken) = this.Respond request override this.SendAsync(request: HttpRequestMessage, _: CancellationToken) = let response = this.Respond request Task.FromResult response type KubeHandler() = inherit HttpMessageHandler() member private _.Respond(request: HttpRequestMessage) = require (request.RequestUri.AbsolutePath = "/api/v1/namespaces/zitadel/secrets/zitadel-login-service-key") "Kubernetes Secret path changed" require (request.Headers.Authorization.Scheme = "Bearer") "Kubernetes bearer token was omitted" require (request.Headers.Authorization.Parameter = "service-account-jwt") "Kubernetes bearer token changed" let encoded = Convert.ToBase64String(Encoding.UTF8.GetBytes "private-key") let response = new HttpResponseMessage(HttpStatusCode.OK) response.Content <- new StringContent($"{{\"data\":{{\"tls.key\":\"{encoded}\"}}}}", Encoding.UTF8, "application/json") response override this.Send(request: HttpRequestMessage, _: CancellationToken) = this.Respond request override this.SendAsync(request: HttpRequestMessage, _: CancellationToken) = Task.FromResult(this.Respond request) let jwtPath = Path.GetTempFileName() try File.WriteAllText(jwtPath, "service-account-jwt") let handler = new VaultHandler() let config = { URL = "http://vault" Role = "bootstrap" JwtPath = jwtPath CaPath = "" } use vaultClient = new HttpClient(handler, true) let store = makeStoreWith vaultClient config require (store.Read "secret/data/test" = Map.ofList (List.singleton ("token", "stored"))) "Vault secret was not decoded" store.Write "secret/data/test" (Map.ofList (List.singleton ("token", "issued"))) require (handler.Seen |> Seq.forall (fun (_, token) -> token = "vault-token")) "Vault operation omitted its client token" use kubeClient = new HttpClient(new KubeHandler(), true) let key = readKubeKeyWith kubeClient { URL = "http://kubernetes" TokenPath = jwtPath CaPath = "" Namespace = "zitadel" Secret = "zitadel-login-service-key" } require (key = "private-key") "Kubernetes Secret key was not decoded" printfn "bootstrap HTTP tests passed" finally File.Delete jwtPath