Merge remote-tracking branch 'origin/main' into feat/allow-k8-dynamic-secret-multi-namespace-and-others

This commit is contained in:
Sheen Capadngan
2025-06-11 14:28:15 +08:00
7 changed files with 58 additions and 44 deletions
+32 -15
View File
@@ -149,8 +149,8 @@ const setupProxyServer = async ({
protocol = GatewayProxyProtocol.Tcp, protocol = GatewayProxyProtocol.Tcp,
httpsAgent httpsAgent
}: { }: {
targetHost: string; targetHost?: string;
targetPort: number; targetPort?: number;
relayPort: number; relayPort: number;
relayHost: string; relayHost: string;
tlsOptions: TGatewayTlsOptions; tlsOptions: TGatewayTlsOptions;
@@ -183,27 +183,44 @@ const setupProxyServer = async ({
let command: string; let command: string;
if (protocol === GatewayProxyProtocol.Http) { if (protocol === GatewayProxyProtocol.Http) {
const targetUrl = `${targetHost}:${targetPort}`; // note(daniel): targetHost MUST include the scheme (https|http) if (!targetHost && !targetPort) {
command = `FORWARD-HTTP ${targetUrl}`; command = `FORWARD-HTTP`;
logger.debug(`Using HTTP proxy mode: ${command.trim()}`); logger.debug(`Using HTTP proxy mode, no target URL provided [command=${command.trim()}]`);
} else {
if (!targetHost || targetPort === undefined) {
throw new BadRequestError({
message: `Target host and port are required for HTTP proxy mode with custom target`
});
}
// extract ca certificate from httpsAgent if present const targetUrl = `${targetHost}:${targetPort}`; // note(daniel): targetHost MUST include the scheme (https|http)
if (httpsAgent && targetHost.startsWith("https://")) { command = `FORWARD-HTTP ${targetUrl}`;
const agentOptions = httpsAgent.options; logger.debug(`Using HTTP proxy mode, custom target URL provided [command=${command.trim()}]`);
if (agentOptions && agentOptions.ca) {
const caCert = Array.isArray(agentOptions.ca) ? agentOptions.ca.join("\n") : agentOptions.ca;
const caB64 = Buffer.from(caCert as string).toString("base64");
command += ` ca=${caB64}`;
const rejectUnauthorized = agentOptions.rejectUnauthorized !== false; // extract ca certificate from httpsAgent if present
command += ` verify=${rejectUnauthorized}`; if (httpsAgent && targetHost.startsWith("https://")) {
const agentOptions = httpsAgent.options;
if (agentOptions && agentOptions.ca) {
const caCert = Array.isArray(agentOptions.ca) ? agentOptions.ca.join("\n") : agentOptions.ca;
const caB64 = Buffer.from(caCert as string).toString("base64");
command += ` ca=${caB64}`;
logger.debug(`Using HTTP proxy mode [command=${command.trim()}]`); const rejectUnauthorized = agentOptions.rejectUnauthorized !== false;
command += ` verify=${rejectUnauthorized}`;
logger.debug(`Using HTTP proxy mode, custom target URL provided [command=${command.trim()}]`);
}
} }
} }
command += "\n"; command += "\n";
} else if (protocol === GatewayProxyProtocol.Tcp) { } else if (protocol === GatewayProxyProtocol.Tcp) {
if (!targetHost || !targetPort) {
throw new BadRequestError({
message: `Target host and port are required for TCP proxy mode`
});
}
// For TCP mode, send FORWARD-TCP with host:port // For TCP mode, send FORWARD-TCP with host:port
command = `FORWARD-TCP ${targetHost}:${targetPort}\n`; command = `FORWARD-TCP ${targetHost}:${targetPort}\n`;
logger.debug(`Using TCP proxy mode: ${command.trim()}`); logger.debug(`Using TCP proxy mode: ${command.trim()}`);
+2 -2
View File
@@ -15,8 +15,8 @@ export enum GatewayHttpProxyActions {
} }
export interface IGatewayProxyOptions { export interface IGatewayProxyOptions {
targetHost: string; targetHost?: string;
targetPort: number; targetPort?: number;
relayHost: string; relayHost: string;
relayPort: number; relayPort: number;
tlsOptions: TGatewayTlsOptions; tlsOptions: TGatewayTlsOptions;
+6 -3
View File
@@ -57,9 +57,12 @@ export const registerServeUI = async (
reply.callNotFound(); reply.callNotFound();
return; return;
} }
// reference: https://github.com/fastify/fastify-static?tab=readme-ov-file#managing-cache-control-headers
// to avoid ui bundle skew on new deployment // This should help avoid caching any chunks (temp fix)
return reply.sendFile("index.html", { maxAge: 0, immutable: false }); void reply.header("Cache-Control", "no-cache, no-store, must-revalidate, private, max-age=0");
void reply.header("Pragma", "no-cache");
void reply.header("Expires", "0");
return reply.sendFile("index.html");
} }
}); });
} }
@@ -120,7 +120,7 @@ export const folderCommitChangesDALFactory = (db: TDbClient) => {
return docs.map((doc) => { return docs.map((doc) => {
// Determine if this is a secret or folder change based on populated fields // Determine if this is a secret or folder change based on populated fields
if (doc.secretKey && doc.secretVersion && doc.secretId) { if (doc.secretKey && doc.secretVersion !== null && doc.secretId) {
return { return {
...doc, ...doc,
resourceType: "secret", resourceType: "secret",
@@ -168,7 +168,7 @@ export const folderCommitChangesDALFactory = (db: TDbClient) => {
); );
return docs return docs
.filter((doc) => doc.secretKey && doc.secretVersion && doc.secretId) .filter((doc) => doc.secretKey && doc.secretVersion !== null && doc.secretId)
.map( .map(
(doc): SecretCommitChange => ({ (doc): SecretCommitChange => ({
...doc, ...doc,
@@ -209,7 +209,7 @@ export const folderCommitChangesDALFactory = (db: TDbClient) => {
); );
return docs return docs
.filter((doc) => doc.folderName && doc.folderVersion && doc.folderChangeId) .filter((doc) => doc.folderName && doc.folderVersion !== null && doc.folderChangeId)
.map( .map(
(doc): FolderCommitChange => ({ (doc): FolderCommitChange => ({
...doc, ...doc,
@@ -72,8 +72,8 @@ export const identityKubernetesAuthServiceFactory = ({
const $gatewayProxyWrapper = async <T>( const $gatewayProxyWrapper = async <T>(
inputs: { inputs: {
gatewayId: string; gatewayId: string;
targetHost: string; targetHost?: string;
targetPort: number; targetPort?: number;
caCert?: string; caCert?: string;
reviewTokenThroughGateway: boolean; reviewTokenThroughGateway: boolean;
}, },
@@ -286,8 +286,6 @@ export const identityKubernetesAuthServiceFactory = ({
data = await $gatewayProxyWrapper( data = await $gatewayProxyWrapper(
{ {
gatewayId: identityKubernetesAuth.gatewayId, gatewayId: identityKubernetesAuth.gatewayId,
targetHost: `/`, // note(daniel): the targetURL will be constructed as `/:0`, which the gateway will handle as a special case, by replacing the /:0, with the internal kubernetes base URL (only when the action header is set to `GatewayHttpProxyActions.UseGatewayK8sServiceAccount`)
targetPort: 0,
reviewTokenThroughGateway: true reviewTokenThroughGateway: true
}, },
tokenReviewCallbackThroughGateway tokenReviewCallbackThroughGateway
+11 -16
View File
@@ -108,19 +108,17 @@ func handleStream(stream quic.Stream, quicConn quic.Connection) {
return return
case "FORWARD-HTTP": case "FORWARD-HTTP":
targetURL := ""
argParts := bytes.Split(args, []byte(" ")) argParts := bytes.Split(args, []byte(" "))
if len(argParts) == 0 {
log.Error().Msg("FORWARD-HTTP requires target URL")
return
}
targetURL := string(argParts[0]) if len(argParts) == 0 || len(argParts[0]) == 0 {
log.Warn().Msg("FORWARD-HTTP used without a target URL.")
// ? note(daniel): special case: if the target URL is "/:0", we don't validate it. } else {
// ? the reason for this is because we want to be able to send requests to the gateway without knowing the actual target URL, and instead let the gateway construct the target URL. targetURL = string(argParts[0])
if targetURL != "/:0" && !isValidURL(targetURL) { if !isValidURL(targetURL) {
log.Error().Msgf("Invalid target URL: %s", targetURL) log.Error().Msgf("Invalid target URL: %s", targetURL)
return return
}
} }
// Parse optional parameters // Parse optional parameters
@@ -208,8 +206,7 @@ func handleHTTPProxy(stream quic.Stream, reader *bufio.Reader, targetURL string,
} }
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", string(token))) req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", string(token)))
log.Info().Msgf("Injected gateway k8s SA auth token in request to %s", targetURL) log.Info().Msgf("Injected gateway k8s SA auth token in request to %s", targetURL)
} else if actionHeader == HttpProxyActionUseGatewayK8sServiceAccount { } else if actionHeader == HttpProxyActionUseGatewayK8sServiceAccount { // will work without a target URL set
// set the ca cert to the pod's k8s service account ca cert: // set the ca cert to the pod's k8s service account ca cert:
caCert, err := os.ReadFile(KUBERNETES_SERVICE_ACCOUNT_CA_CERT_PATH) caCert, err := os.ReadFile(KUBERNETES_SERVICE_ACCOUNT_CA_CERT_PATH)
if err != nil { if err != nil {
@@ -218,9 +215,7 @@ func handleHTTPProxy(stream quic.Stream, reader *bufio.Reader, targetURL string,
} }
caCertPool := x509.NewCertPool() caCertPool := x509.NewCertPool()
appendSuccess := caCertPool.AppendCertsFromPEM(caCert) if ok := caCertPool.AppendCertsFromPEM(caCert); !ok {
if !appendSuccess {
stream.Write([]byte(buildHttpInternalServerError("failed to parse k8s sa ca cert"))) stream.Write([]byte(buildHttpInternalServerError("failed to parse k8s sa ca cert")))
continue continue
} }
@@ -32,7 +32,8 @@ Infisical needs an initial AWS IAM user with the required permissions to create
"iam:ListUserPolicies", "iam:ListUserPolicies",
"iam:PutUserPolicy", "iam:PutUserPolicy",
"iam:AddUserToGroup", "iam:AddUserToGroup",
"iam:RemoveUserFromGroup" "iam:RemoveUserFromGroup",
"iam:TagUser"
], ],
"Resource": ["*"] "Resource": ["*"]
} }