From 98d20b7ca95d08214804c7eb06a6feea6c32a783 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Portela=20Afonso?= Date: Fri, 24 Mar 2023 12:19:43 +0000 Subject: [PATCH] feat(authentication): support for exec kube config --- .../Config/KubernetesClientConfig.swift | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/Sources/SwiftkubeClient/Config/KubernetesClientConfig.swift b/Sources/SwiftkubeClient/Config/KubernetesClientConfig.swift index c6f67cd..ea51086 100644 --- a/Sources/SwiftkubeClient/Config/KubernetesClientConfig.swift +++ b/Sources/SwiftkubeClient/Config/KubernetesClientConfig.swift @@ -336,6 +336,72 @@ private extension AuthInfo { } catch { logger?.warning("Error initializing authentication from client certificate: \(error)") } + + #if os(Linux) || os(macOS) + do { + if let exec { + let outputData = try run(command: exec.command, arguments: exec.args) + + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .iso8601 + let credential = try decoder.decode(ExecCredential.self, from: outputData) + + return .bearer(token: credential.status.token) + } + } catch { + logger?.warning("Error initializing authentication from exec \(error)") + } + #endif return nil } } + +// MARK: - ExecCredential + +// It seems that AWS doesn't implement properly the model for client.authentication.k8s.io/v1beta1 +// Acordingly with the doc https://kubernetes.io/docs/reference/config-api/client-authentication.v1beta1/ +// ExecCredential.Spec.interactive is required as long as the ones in the Status object. +internal struct ExecCredential: Decodable { + let apiVersion: String + let kind: String + let spec: Spec + let status: Status +} + +internal extension ExecCredential { + struct Spec: Decodable { + let cluster: Cluster? + let interactive: Bool? + } + + struct Status: Decodable { + let expirationTimestamp: Date + let token: String + let clientCertificateData: String? + let clientKeyData: String? + } +} + +#if os(Linux) || os(macOS) + internal func run(command: String, arguments: [String]? = nil) throws -> Data { + func run(_ command: String, _ arguments: [String]?) throws -> Data { + let task = Process() + task.executableURL = URL(fileURLWithPath: command) + task.arguments = arguments + + let pipe = Pipe() + task.standardOutput = pipe + + try task.run() + + return pipe.fileHandleForReading.availableData + } + + func resolve(command: String) throws -> String { + try String(decoding: + run("/usr/bin/which", ["\(command)"]), as: UTF8.self).trimmingCharacters(in: .whitespacesAndNewlines) + } + + return try run(resolve(command: command), arguments) + } +#endif