-
Notifications
You must be signed in to change notification settings - Fork 173
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(security): Add more Fiat abstractions (#1138)
* feat(security): Add more Fiat abstractions Related to spinnaker/spinnaker#6911 This adds a copy of the Authorization enum from fiat-core, an AbstractPermissionEvaluator base class which fiat-api will be updated to use, some extensions to AccessControlled, and some other security utility functions. * fix(security): Make Authorization parsing robust
- Loading branch information
Showing
11 changed files
with
617 additions
and
1 deletion.
There are no files selected for viewing
79 changes: 79 additions & 0 deletions
79
kork-security/src/main/java/com/netflix/spinnaker/security/AbstractPermissionEvaluator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
/* | ||
* Copyright 2023 Apple, Inc. | ||
* | ||
* Licensed 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 com.netflix.spinnaker.security; | ||
|
||
import java.io.Serializable; | ||
import org.springframework.security.access.PermissionEvaluator; | ||
import org.springframework.security.core.Authentication; | ||
|
||
/** | ||
* Base implementation for permission evaluators that support {@link AccessControlled} domain | ||
* objects. | ||
*/ | ||
public abstract class AbstractPermissionEvaluator implements PermissionEvaluator { | ||
|
||
@Override | ||
public boolean hasPermission( | ||
Authentication authentication, Object targetDomainObject, Object permission) { | ||
if (isDisabled()) { | ||
return true; | ||
} | ||
if (authentication == null || targetDomainObject == null) { | ||
return false; | ||
} | ||
if (SpinnakerAuthorities.isAdmin(authentication)) { | ||
return true; | ||
} | ||
if (targetDomainObject instanceof AccessControlled) { | ||
return ((AccessControlled) targetDomainObject).isAuthorized(authentication, permission); | ||
} | ||
return false; | ||
} | ||
|
||
@Override | ||
public boolean hasPermission( | ||
Authentication authentication, Serializable targetId, String targetType, Object permission) { | ||
if (isDisabled()) { | ||
return true; | ||
} | ||
return hasPermission( | ||
SpinnakerUsers.getUserId(authentication), targetId, targetType, permission); | ||
} | ||
|
||
/** | ||
* Indicates whether permission evaluation is disabled. When this is true, {@code hasPermission} | ||
* calls should return true. This should be overridden to allow for toggling this evaluator at | ||
* runtime. | ||
*/ | ||
protected boolean isDisabled() { | ||
return false; | ||
} | ||
|
||
/** | ||
* Alternative method for evaluating a permission where only the identifier of the user and target | ||
* object is available, rather than the authenticated user and target objects themselves. | ||
* | ||
* @param username identifier for user to check permissions for | ||
* @param targetId identifier of the target resource to check permissions | ||
* @param targetType the type of the target resource being checked | ||
* @param permission the permission being validated | ||
* @return true if the permission is granted | ||
*/ | ||
public abstract boolean hasPermission( | ||
String username, Serializable targetId, String targetType, Object permission); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
45 changes: 45 additions & 0 deletions
45
kork-security/src/main/java/com/netflix/spinnaker/security/Authorization.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
/* | ||
* Copyright 2022 Apple Inc. | ||
* | ||
* Licensed 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 com.netflix.spinnaker.security; | ||
|
||
import java.util.Locale; | ||
import javax.annotation.Nullable; | ||
import org.springframework.security.core.Authentication; | ||
|
||
/** | ||
* Defines types of authorizations supported by {@link AccessControlled#isAuthorized(Authentication, | ||
* Object)}. | ||
*/ | ||
public enum Authorization { | ||
READ, | ||
WRITE, | ||
EXECUTE, | ||
CREATE, | ||
; | ||
|
||
public static @Nullable Authorization parse(@Nullable Object o) { | ||
if (o == null) { | ||
return null; | ||
} | ||
String name = o.toString().toUpperCase(Locale.ROOT); | ||
try { | ||
return valueOf(name); | ||
} catch (IllegalArgumentException e) { | ||
return null; | ||
} | ||
} | ||
} |
32 changes: 32 additions & 0 deletions
32
kork-security/src/main/java/com/netflix/spinnaker/security/AuthorizationMapControlled.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
/* | ||
* Copyright 2023 Apple, Inc. | ||
* | ||
* Licensed 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 com.netflix.spinnaker.security; | ||
|
||
import javax.annotation.Nullable; | ||
|
||
/** | ||
* Common interface for access-controlled classes which use a permission map of {@link | ||
* Authorization} enums. | ||
*/ | ||
public interface AuthorizationMapControlled extends PermissionMapControlled<Authorization> { | ||
@Nullable | ||
@Override | ||
default Authorization valueOf(@Nullable Object authorization) { | ||
return Authorization.parse(authorization); | ||
} | ||
} |
52 changes: 52 additions & 0 deletions
52
kork-security/src/main/java/com/netflix/spinnaker/security/PermissionMapControlled.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
/* | ||
* Copyright 2023 Apple Inc. | ||
* | ||
* Licensed 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 com.netflix.spinnaker.security; | ||
|
||
import com.netflix.spinnaker.kork.annotations.Alpha; | ||
import java.util.Map; | ||
import java.util.Set; | ||
import javax.annotation.Nonnull; | ||
import javax.annotation.Nullable; | ||
import org.springframework.security.core.Authentication; | ||
|
||
/** | ||
* Common interface for access-controlled classes which use a permission map. | ||
* | ||
* @param <Authorization> Authorization enum type | ||
*/ | ||
@Alpha | ||
public interface PermissionMapControlled<Authorization extends Enum<Authorization>> | ||
extends AccessControlled { | ||
@Nullable | ||
Authorization valueOf(@Nullable Object authorization); | ||
|
||
@Nonnull | ||
default Map<Authorization, Set<String>> getPermissions() { | ||
return Map.of(); | ||
} | ||
|
||
@Override | ||
default boolean isAuthorized(Authentication authentication, Object authorization) { | ||
Authorization auth = valueOf(authorization); | ||
if (auth == null) { | ||
return false; | ||
} | ||
Set<String> permittedRoles = getPermissions().getOrDefault(auth, Set.of()); | ||
return permittedRoles.isEmpty() | ||
|| SpinnakerAuthorities.hasAnyRole(authentication, permittedRoles); | ||
} | ||
} |
90 changes: 90 additions & 0 deletions
90
kork-security/src/main/java/com/netflix/spinnaker/security/SpinnakerAuthorities.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
/* | ||
* Copyright 2022 Apple, Inc. | ||
* | ||
* Licensed 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 com.netflix.spinnaker.security; | ||
|
||
import java.util.Collection; | ||
import java.util.List; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.Stream; | ||
import javax.annotation.Nonnull; | ||
import javax.annotation.Nullable; | ||
import org.springframework.security.core.Authentication; | ||
import org.springframework.security.core.GrantedAuthority; | ||
import org.springframework.security.core.authority.SimpleGrantedAuthority; | ||
|
||
/** | ||
* Constants and utilities for working with Spring Security GrantedAuthority objects specific to | ||
* Spinnaker and Fiat. Spinnaker-specific roles are represented here as granted authorities with the | ||
* {@code SPINNAKER_} prefix. | ||
*/ | ||
public class SpinnakerAuthorities { | ||
private static final String ROLE_PREFIX = "ROLE_"; | ||
|
||
public static final String ADMIN = "SPINNAKER_ADMIN"; | ||
/** Granted authority for Spinnaker administrators. */ | ||
public static final GrantedAuthority ADMIN_AUTHORITY = new SimpleGrantedAuthority(ADMIN); | ||
|
||
/** Granted authority for anonymous users. */ | ||
public static final GrantedAuthority ANONYMOUS_AUTHORITY = forRoleName("ANONYMOUS"); | ||
|
||
/** Creates a granted authority corresponding to the provided name of a role. */ | ||
@Nonnull | ||
public static GrantedAuthority forRoleName(@Nonnull String role) { | ||
return new SimpleGrantedAuthority(ROLE_PREFIX + role); | ||
} | ||
|
||
/** Checks if the given user is a Spinnaker admin. */ | ||
public static boolean isAdmin(@Nullable Authentication authentication) { | ||
return authentication != null | ||
&& authentication.getAuthorities().contains(SpinnakerAuthorities.ADMIN_AUTHORITY); | ||
} | ||
|
||
/** Checks if the given user has the provided role. */ | ||
public static boolean hasRole(@Nullable Authentication authentication, @Nonnull String role) { | ||
return authentication != null && streamRoles(authentication).anyMatch(role::equals); | ||
} | ||
|
||
/** Checks if the given user has any of the provided roles. */ | ||
public static boolean hasAnyRole( | ||
@Nullable Authentication authentication, @Nonnull Collection<String> roles) { | ||
return authentication != null && streamRoles(authentication).anyMatch(roles::contains); | ||
} | ||
|
||
/** Gets the list of roles assigned to the given user. */ | ||
@Nonnull | ||
public static List<String> getRoles(@Nullable Authentication authentication) { | ||
if (authentication == null) { | ||
return List.of(); | ||
} | ||
return streamRoles(authentication).distinct().collect(Collectors.toList()); | ||
} | ||
|
||
@Nonnull | ||
private static Stream<String> streamRoles(@Nonnull Authentication authentication) { | ||
return authentication.getAuthorities().stream() | ||
.filter(SpinnakerAuthorities::isRole) | ||
.map(SpinnakerAuthorities::getRole); | ||
} | ||
|
||
private static boolean isRole(@Nonnull GrantedAuthority authority) { | ||
return authority.getAuthority().startsWith(ROLE_PREFIX); | ||
} | ||
|
||
private static String getRole(@Nonnull GrantedAuthority authority) { | ||
return authority.getAuthority().substring(ROLE_PREFIX.length()); | ||
} | ||
} |
45 changes: 45 additions & 0 deletions
45
kork-security/src/main/java/com/netflix/spinnaker/security/SpinnakerUsers.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
/* | ||
* Copyright 2023 Apple, Inc. | ||
* | ||
* Licensed 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 com.netflix.spinnaker.security; | ||
|
||
import com.netflix.spinnaker.kork.annotations.NonnullByDefault; | ||
import javax.annotation.Nullable; | ||
import org.springframework.security.core.Authentication; | ||
import org.springframework.security.core.context.SecurityContextHolder; | ||
|
||
/** Constants and utilities related to Spinnaker users (AKA principals). */ | ||
@NonnullByDefault | ||
public class SpinnakerUsers { | ||
/** String constant for the anonymous userid. */ | ||
public static final String ANONYMOUS = "anonymous"; | ||
|
||
/** Gets the userid of the provided authentication token. */ | ||
public static String getUserId(@Nullable Authentication authentication) { | ||
return authentication != null ? authentication.getName() : ANONYMOUS; | ||
} | ||
|
||
/** Gets the current Spinnaker userid. */ | ||
public static String getCurrentUserId() { | ||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); | ||
if (authentication != null) { | ||
return getUserId(authentication); | ||
} | ||
// fall back to request header context if relevant (AuthenticatedRequestFilter) | ||
return AuthenticatedRequest.getSpinnakerUser().orElse(ANONYMOUS); | ||
} | ||
} |
Oops, something went wrong.