Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions gateway-provider-security-k8s/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.knox</groupId>
<artifactId>gateway</artifactId>
<version>3.0.0-SNAPSHOT</version>
</parent>

<artifactId>gateway-provider-security-k8s</artifactId>
<name>gateway-provider-security-k8s</name>
<description>Kubernetes-aware PreAuth trust validators (e.g., SPIFFE ID to ServiceAccount annotation matching).</description>

<dependencies>
<dependency>
<groupId>org.apache.knox</groupId>
<artifactId>gateway-provider-security-preauth</artifactId>
</dependency>
<dependency>
<groupId>org.apache.knox</groupId>
<artifactId>gateway-i18n</artifactId>
</dependency>
<dependency>
<groupId>org.apache.knox</groupId>
<artifactId>gateway-spi</artifactId>
</dependency>

<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>

<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-client</artifactId>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-client-api</artifactId>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-model-core</artifactId>
</dependency>

<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>

<dependency>
<groupId>org.apache.knox</groupId>
<artifactId>gateway-test-utils</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.knox.gateway.preauth.k8s;

public class K8sLookupException extends RuntimeException {

public K8sLookupException(Throwable cause) {
super(cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.knox.gateway.preauth.k8s;

import org.apache.knox.gateway.preauth.filter.AbstractPreAuthFederationFilter;

import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.security.Principal;
import java.time.Duration;
import java.util.Set;

public class K8sPreAuthFederationFilter extends AbstractPreAuthFederationFilter {
private String userHeader = ServiceAccountValidator.USER_HEADER_DEFAULT;
private K8sServiceAccountResolver resolver;

@Override
public void init(FilterConfig filterConfig) throws ServletException {
super.init(filterConfig);
String configured = filterConfig.getInitParameter(ServiceAccountValidator.USER_HEADER_PARAM);
if (configured != null && !configured.isEmpty()) {
userHeader = configured;
}

long ttlSeconds = longParam(filterConfig,
ServiceAccountValidator.CACHE_TTL_SECONDS_PARAM,
ServiceAccountValidator.CACHE_TTL_SECONDS_DEFAULT);
long maxSize = longParam(filterConfig,
ServiceAccountValidator.CACHE_MAX_SIZE_PARAM,
ServiceAccountValidator.CACHE_MAX_SIZE_DEFAULT);
if (ttlSeconds <= 0) {
throw new ServletException(ServiceAccountValidator.CACHE_TTL_SECONDS_PARAM
+ " must be > 0 (got " + ttlSeconds + ")");
}
if (maxSize <= 0) {
throw new ServletException(ServiceAccountValidator.CACHE_MAX_SIZE_PARAM
+ " must be > 0 (got " + maxSize + ")");
}

if (resolver == null) {
resolver = createResolver(Duration.ofSeconds(ttlSeconds), maxSize);
}
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
request.setAttribute(ServiceAccountValidator.RESOLVER_REQUEST_ATTR, resolver);
try {
super.doFilter(request, response, chain);
} finally {
request.removeAttribute(ServiceAccountValidator.RESOLVER_REQUEST_ATTR);
}
}

@Override
public void destroy() {
if (resolver != null) {
resolver.close();
resolver = null;
}
super.destroy();
}

@Override
protected String getPrimaryPrincipal(HttpServletRequest httpRequest) {
return httpRequest.getHeader(userHeader);
}

@Override
protected void addGroupPrincipals(HttpServletRequest request, Set<Principal> principals) {
}

@Override
protected String getValidationFailureMessage() {
return "Kubernetes pre-authentication failed: SPIFFE/ServiceAccount validation rejected the request.";
}

@Override
protected String getMissingPrincipalMessage() {
return "Missing required user header for Kubernetes pre-authentication.";
}

protected K8sServiceAccountResolver createResolver(Duration ttl, long maxSize) {
return new K8sServiceAccountResolver(ttl, maxSize);
}

private static long longParam(FilterConfig cfg, String name, long defaultValue) {
final String v = cfg.getInitParameter(name);
if (v == null || v.isEmpty()) {
return defaultValue;
}
try {
return Long.parseLong(v.trim());
} catch (NumberFormatException e) {
return defaultValue;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.knox.gateway.preauth.k8s;

import org.apache.knox.gateway.i18n.messages.Message;
import org.apache.knox.gateway.i18n.messages.MessageLevel;
import org.apache.knox.gateway.i18n.messages.Messages;
import org.apache.knox.gateway.i18n.messages.StackTrace;

@Messages(logger = "org.apache.knox.gateway.preauth.k8s")
public interface K8sPreAuthMessages {

@Message(level = MessageLevel.WARN, text = "Rejecting request: SPIFFE header ''{0}'' is missing")
void missingSpiffeHeader(String headerName);

@Message(level = MessageLevel.WARN, text = "Rejecting request: user header ''{0}'' is missing")
void missingUserHeader(String headerName);

@Message(level = MessageLevel.WARN,
text = "Rejecting request: SPIFFE header value ''{0}'' is not a parseable k8s SPIFFE ID (asserted user ''{1}'')")
void unparseableSpiffeId(String spiffeRaw, String assertedUser);

@Message(level = MessageLevel.WARN,
text = "Rejecting request: ServiceAccount {0}/{1} has no ''{2}'' annotation (asserted user ''{3}'', SPIFFE ID ''{4}'')")
void missingServiceAccountAnnotation(String namespace,
String serviceAccount,
String annotationKey,
String assertedUser,
String spiffeId);

@Message(level = MessageLevel.WARN,
text = "Rejecting request: asserted user ''{0}'' does not match ServiceAccount {1}/{2} ''{3}'' (SPIFFE ID ''{4}'')")
void assertedUserDoesNotMatchAnnotation(String assertedUser,
String namespace,
String serviceAccount,
String annotationKey,
String spiffeId);

@Message(level = MessageLevel.ERROR,
text = "Failed to load ServiceAccount {0}/{1} from Kubernetes API: {2}")
void failedToLoadServiceAccount(String namespace,
String serviceAccount,
@StackTrace(level = MessageLevel.ERROR) Throwable e);
}
Loading
Loading