Feature/ctx aware and access controller2 (#1456)

* Introduce the AccessControlManager and use if for the TargetManagement and TargetTypeManagement.

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>

* Extend the access control manager by an API to serialize the current active context and persist it for scheduled background operations like auto-assignment.

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>

* Verify modification is permitted before performing automatic assignment

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>

* Start with controlling distribution set type access. Perform some refactoring.

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>

* Support distribution set access control. Increase character limit to 512 chars for access control context. Refactor default implementations.

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>

* Introduce ContextRunner and define admin execution to check for duplicates before creating/updating entities.

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>

* Introduce Software Module, Module Type and Artifact control management. Fix tests.

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>

* Introduce access controlling test base. Add first test verifying the read operations for target types.

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>

* Finalize target type access controlling test.

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>

* Introduce ContextRunnerTest and TargetAccessControllingTest.
Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>

* Introduce DistributionSetAccessControllingTest and fix missing access control specifications.

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>

* Extend test cases. Include only updatable targets into rollout.

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>

* Fix action visibility.

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>

* Modifiable->Updatable & UPDATE check where needed

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>

* ContextRunner superseded by ContextAware

+ ContextRunner remaned to ContextAware (move as a cenral entry/concept).
  It now extends (and replace) TenantAware
+ SecurityContextTenantAware becomes ContextAware
+ Pluggable serialization mechanism
  (default Java serialization of contexts) for SecurityContextTenantAware
  (using SecurityContextSerializer)
+ AccessControl methods are added to ensure no entities fill be retrieved
  just to call access control - so, if all permitted - no additional db
  queries will be made
+ &lt;repo type&gt;AccessControl classes removed and replaced with
  AccessControl &lt;repo type&gt; generics
+ AccessControlService removed - every AccessControl is registered and
  overiden independently
+ access_control_context in DB increased to 4k (in order to support java
  security context serialization)
+ needed adaptaion of implemtation and tests done

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>

* Refactor SoftModules & DistSets

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>

* Refactoring of the Repositories

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>

* Repostiotory level permissions

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>

* Improvements

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>

* Simplification of AccessControl interface

* Simplifications & management package

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>

* Implementation improvements

+ Artifact management & repo reviewed and tuned
+ Action(Status) management & repo reviewed and tuned
+ SoftwareModule(Type/Meta) management & repo reviewed and tuned
+ DistributionSet(Type/Tag/Meta) management(+Invalidation) & repo reviewed and tuned
+ Target(Tag/Type/Meta) management & repo reviewed and tuned
+ TargetQueryFilter management & repo reviewed and tuned

* Apply suggestions from code review

Suggestions accepted. Thanks @herdt-michael

Co-authored-by: Michael Herdt <michael.herdt@bosch.com>

* Apply suggestions from code review 2

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>

---------

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>
Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>
Co-authored-by: Michael Herdt <Michael.Herdt@bosch.com>
This commit is contained in:
Avgustin Marinov
2023-11-16 11:07:06 +02:00
committed by GitHub
parent 8d487fde33
commit b982039a74
170 changed files with 5371 additions and 3227 deletions

View File

@@ -0,0 +1,104 @@
/**
* Copyright (c) 2023 Bosch.IO GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.security;
import org.springframework.security.core.context.SecurityContext;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Base64;
import java.util.Objects;
public interface SecurityContextSerializer {
/**
* Serializer that do not serialize (returns null on {@link #serialize(SecurityContext)}) and
* throws exception on {@link #deserialize(String)}.
*/
SecurityContextSerializer NOP = new Nop();
/**
* Serializer the uses Java serialization of {@link java.io.Serializable} objects.
* <p/>
* Note that serialized via java serialization context might become unreadable if incompatible
* changes are made to the object classes.
*/
SecurityContextSerializer JAVA_SERIALIZATION = new JavaSerialization();
/**
* Return security context as string (could be just a reference)
*
* @param securityContext the security context
* @return the securityContext as string
*/
String serialize(SecurityContext securityContext);
/**
* Deserialize security context
*
* @param securityContextString string representing the security context
* @return deserialized security context
*/
SecurityContext deserialize(String securityContextString);
/**
* Empty implementation. Could be used if the serialization shall not be used.
* It returns <code>null</code> as serialized context and throws exception if
* someone try to deserialize anything.
*/
class Nop implements SecurityContextSerializer {
private Nop() {}
@Override
public String serialize(final SecurityContext securityContext) {
return null;
}
@Override
public SecurityContext deserialize(String securityContextString) {
throw new UnsupportedOperationException();
}
}
/**
* Implementation based on the java serialization.
*/
class JavaSerialization implements SecurityContextSerializer {
private JavaSerialization() {}
@Override
public String serialize(final SecurityContext securityContext) {
Objects.requireNonNull(securityContext);
try (final ByteArrayOutputStream baos = new ByteArrayOutputStream();
final ObjectOutputStream oos = new ObjectOutputStream(baos)) {
oos.writeObject(securityContext);
oos.flush();
return Base64.getEncoder().encodeToString(baos.toByteArray());
} catch (final IOException e) {
throw new RuntimeException(e);
}
}
@Override
public SecurityContext deserialize(String securityContextString) {
Objects.requireNonNull(securityContextString);
try (final ByteArrayInputStream bais = new ByteArrayInputStream(Base64.getDecoder().decode(securityContextString));
final ObjectInputStream ois = new ObjectInputStream(bais)) {
return (SecurityContext) ois.readObject();
} catch (final IOException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
}

View File

@@ -9,38 +9,58 @@
*/
package org.eclipse.hawkbit.security;
import java.io.Serial;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.ContextAware;
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails;
import org.eclipse.hawkbit.im.authentication.UserPrincipal;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.tenancy.UserAuthoritiesResolver;
import org.springframework.lang.Nullable;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
/**
* A {@link TenantAware} implementation which retrieves the ID of the tenant
* A {@link ContextAware} (hence of {@link TenantAware}) that uses spring security context propagation
* mechanisms and which retrieves the ID of the tenant
* from the {@link SecurityContext#getAuthentication()}
* {@link Authentication#getDetails()} which holds the
* {@link TenantAwareAuthenticationDetails} object.
*
*/
public class SecurityContextTenantAware implements TenantAware {
public class SecurityContextTenantAware implements ContextAware {
public static final String SYSTEM_USER = "system";
private static final Collection<? extends GrantedAuthority> SYSTEM_AUTHORITIES = Collections
.singletonList(new SimpleGrantedAuthority(SpringEvalExpressions.SYSTEM_ROLE));
private final UserAuthoritiesResolver authoritiesResolver;
private final SecurityContextSerializer securityContextSerializer;
/**
* Creates the {@link SecurityContextTenantAware} based on the given
* {@link UserAuthoritiesResolver}.
*
* @param authoritiesResolver
* Resolver to retrieve the authorities for a given user. Must
* not be <code>null</code>..
*/
public SecurityContextTenantAware(final UserAuthoritiesResolver authoritiesResolver) {
this.authoritiesResolver = authoritiesResolver;
this.securityContextSerializer = SecurityContextSerializer.NOP;
}
/**
* Creates the {@link SecurityContextTenantAware} based on the given
@@ -49,9 +69,12 @@ public class SecurityContextTenantAware implements TenantAware {
* @param authoritiesResolver
* Resolver to retrieve the authorities for a given user. Must
* not be <code>null</code>.
* @param securityContextSerializer
* Serializer that is used to serialize / deserialize {@link SecurityContext}s.
*/
public SecurityContextTenantAware(final UserAuthoritiesResolver authoritiesResolver) {
public SecurityContextTenantAware(final UserAuthoritiesResolver authoritiesResolver, @Nullable final SecurityContextSerializer securityContextSerializer) {
this.authoritiesResolver = authoritiesResolver;
this.securityContextSerializer = securityContextSerializer == null ? SecurityContextSerializer.NOP : securityContextSerializer;
}
@Override
@@ -90,12 +113,39 @@ public class SecurityContextTenantAware implements TenantAware {
@Override
public <T> T runAsTenantAsUser(final String tenant, final String username, final TenantRunner<T> tenantRunner) {
Objects.requireNonNull(tenant);
Objects.requireNonNull(username);
final List<SimpleGrantedAuthority> authorities = runAsSystem(
() -> authoritiesResolver.getUserAuthorities(tenant, username).stream().map(SimpleGrantedAuthority::new)
.collect(Collectors.toList()));
return runInContext(buildUserSecurityContext(tenant, username, authorities), tenantRunner);
}
@Override
public Optional<String> getCurrentContext() {
return Optional.ofNullable(SecurityContextHolder.getContext()).map(securityContextSerializer::serialize);
}
@Override
public <T, R> R runInContext(final String serializedContext, final Function<T, R> function, final T t) {
Objects.requireNonNull(serializedContext);
Objects.requireNonNull(function);
final SecurityContext securityContext = securityContextSerializer.deserialize(serializedContext);
Objects.requireNonNull(securityContext);
final SecurityContext originalContext = SecurityContextHolder.getContext();
if (Objects.equals(securityContext, originalContext)) {
return function.apply(t);
} else {
SecurityContextHolder.setContext(securityContext);
try {
return function.apply(t);
} finally {
SecurityContextHolder.setContext(originalContext);
}
}
}
private static <T> T runInContext(final SecurityContext context, final TenantRunner<T> tenantRunner) {
final SecurityContext originalContext = SecurityContextHolder.getContext();
try {
@@ -122,7 +172,7 @@ public class SecurityContextTenantAware implements TenantAware {
private static SecurityContext buildUserSecurityContext(final String tenant, final String username,
final Collection<? extends GrantedAuthority> authorities) {
final SecurityContextImpl securityContext = new SecurityContextImpl();
final SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
securityContext.setAuthentication(new AuthenticationDelegate(
SecurityContextHolder.getContext().getAuthentication(), tenant, username, authorities));
return securityContext;
@@ -134,6 +184,7 @@ public class SecurityContextTenantAware implements TenantAware {
* a specific tenant and user.
*/
private static final class AuthenticationDelegate implements Authentication {
@Serial
private static final long serialVersionUID = 1L;
private final Authentication delegate;
@@ -151,12 +202,7 @@ public class SecurityContextTenantAware implements TenantAware {
@Override
public boolean equals(final Object another) {
if (delegate != null) {
return delegate.equals(another);
} else if (another == null) {
return true;
}
return false;
return Objects.equals(delegate, another);
}
@Override