Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TH2-5132] behaviour.permittedToRemoveNamespace option #92

Merged
merged 18 commits into from
Apr 17, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
38 changes: 37 additions & 1 deletion API.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,10 @@ Response body is empty, with `204 No Content` response code.
##
##
### Secrets API

`/secrets/**` are accessible for users with admin role only.
Please look at the README for `http` configuration to configure admin users.

__GET/secrets/{schema}__

__Returns:__
Expand All @@ -219,6 +223,12 @@ Set containing names of keys of custom secrets in specified schema.

__Response body example:__

__CURL example:__

`curl -u "admin:password" 'http://my-cluster:30000/editor/backend/secrets/my-schema'`

`["cassandraPassword"]`

If there are two keys in custom secrets file of this schema
```json
[
Expand Down Expand Up @@ -254,6 +264,12 @@ __Response body example:__
"key2"
]
```

__CURL example:__

`curl curl -X PUT 'http://my-cluster:30000/editor/backend/secrets/my-schema'
-u "admin:password" -H 'Content-Type: application/json'
-d '[{"key":"key1","data":"dXBkYXRlZHZhbHVl","key":"key2","data":"c29tZS1zZWNyZXQtdmFsdWU="}]'`
##
__DELETE/secrets/{schema}__

Expand All @@ -275,4 +291,24 @@ __Response body example:__
"key1"
]
```
##

##
##
### Namespace API

`/namespace/**` endpoints are accessible for users with admin role only.
Please look at the README for `http` configuration to configure admin users.

__DELETE/namespace/{schema}__

Deletes Kubernetes namespace related to `schema` if related git branch is deleted or spec.k8s-propagation: deny

__Returns:__

Name of deleted Kubernetes namespace.

__Response body example:__
`th2-my-schema`

__CURL example:__
`curl -X DELETE 'http://my-cluster:30000/editor/backend/namespace/my-schema' -u "admin:password" `
31 changes: 22 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,24 @@ infra-mgr configuration is given with *config.yml* file that should be on the cl
# individual ConfigMaps for components to be copied from infra-mgr namespace to schema namespace
# this ConfigMaps will be populated with schema specific data before copying to target namespace

behaviour:
permittedToRemoveNamespace: true
# Has infra-mgr got permission to remove Kubernetes namespace when
# branch is disabled (spec.k8s-propagation: deny) or removed.
# Infra-manager removes Kubernetes namespace when this option is true otherwise
# stops maintenance for the namespace without deleting any resources.
# Maintenance is continued when user enable or create the branch related to namespace: `<prefix><branch name>`
# Default value is `true`
behaviour:
permittedToRemoveNamespace: true
# Has infra-mgr got permission to remove Kubernetes namespace when
# branch is disabled (spec.k8s-propagation: deny) or removed.
# Infra-manager removes Kubernetes namespace when this option is true otherwise
# stops maintenance for the namespace without deleting any resources.
# Maintenance is continued when user enable or create the branch related to namespace: `<prefix><branch name>`
# Default value is `true`

http:
adminAccounts:
jack: $2a$10$OvtVdHUf1/n1YL8lrf.69e3mCLA0HLWjUusHmSSxC6dVcEfIvJM6a
emily: $2a$10$Sj2H49Lav.3BsAoq660KAeoFPQFoat8DXlpuhTtID/jJUixzZDRB6
# Map of username to encrypted by BCrypt (strength >= 10) password pairs.
# @see <a href="https://en.wikipedia.org/wiki/Bcrypt">BCrypt</a>
# This is required parameters because user must have admin role to
# access the `/secrets/**` and `/namespace/**` endpoints.
# example: `curl -u "<admin name>:<password>" 'http://localhost:8080/secrets/demo'`
```
##
## For API documentation please refer to
Expand All @@ -107,4 +117,7 @@ infra-mgr configuration is given with *config.yml* file that should be on the cl

### 2.4.0
+ Added `behaviour.permittedToRemoveNamespace` option
+ Added value format check for secret when user uploads secrets via HTTP API
+ Added `http.adminAccounts` required option
+ `/secrets/**` and `/namespace/**` are accessible for users with admin role only
+ `curl -X PUT 'http://localhost:8080/secrets/demo' -u "<admin name>:<password>" ...` endpoint
check secret value format when user uploads secrets via HTTP API
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ release_version = 2.4.0
springboot_version = 3.1.0
kotlin_version = 1.8.22
detekt_version = 1.22.0
owaspVersion = 8.1.2
owaspVersion = 8.1.2
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.exactpro.th2.inframgr;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
Expand All @@ -29,19 +30,19 @@
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

import java.io.IOException;
import java.util.List;
import java.util.Map;

import static org.springframework.security.config.Customizer.withDefaults;

@Configuration
@EnableWebSecurity
@SuppressWarnings("unused")
public class BasicAuthConfig {
public class BasicAuthConfiguration {

private static final String ADMIN_ROLE = "ADMIN";

public static final PasswordEncoder PASSWORD_ENCODER = new BCryptPasswordEncoder();
@Autowired
private Config config;

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
Expand All @@ -57,9 +58,13 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
}

@Bean
public UserDetailsService userDetailsService() throws IOException {
Config config = Config.getInstance();
List<UserDetails> admins = config.getHttp().getAdminAccounts().entrySet().stream()
public UserDetailsService userDetailsService() {
Map<String, String> adminAccounts = config.getHttp().getAdminAccounts();
if (adminAccounts.isEmpty()) {
throw new IllegalStateException("'http.adminAccounts' mustn't be empty");
}

List<UserDetails> admins = adminAccounts.entrySet().stream()
.map(entry -> User.builder()
.username(entry.getKey())
.password(entry.getValue())
Expand All @@ -71,6 +76,6 @@ public UserDetailsService userDetailsService() throws IOException {

@Bean
public PasswordEncoder passwordEncoder() {
return PASSWORD_ENCODER;
return new BCryptPasswordEncoder();
}
}
20 changes: 7 additions & 13 deletions src/main/java/com/exactpro/th2/inframgr/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import java.nio.file.Files;
import java.nio.file.Path;

// TODO: instant this class as spring @Bean instead of singleton
public class Config {
Nikita-Smirnov-Exactpro marked this conversation as resolved.
Show resolved Hide resolved

private static final Logger LOGGER = LoggerFactory.getLogger(Config.class);
Expand Down Expand Up @@ -127,17 +128,9 @@ public void setKubernetes(K8sConfig kubernetes) {

private Config() {}

public static Config getInstance() throws IOException {
if (instance == null) {
synchronized (Config.class) {
if (instance == null) {
Path file = CONFIG_DIR.resolve(CONFIG_FILE);
instance = readConfiguration(file);
}
}
}

return instance;
public static Config instance() throws IOException {
Nikita-Smirnov-Exactpro marked this conversation as resolved.
Show resolved Hide resolved
Path file = CONFIG_DIR.resolve(CONFIG_FILE);
return readConfiguration(file);
}

private static void parseFile(File file, ObjectMapper mapper, Object object) throws IOException {
Expand Down Expand Up @@ -169,8 +162,9 @@ static Config readConfiguration(Path configFile) throws IOException {
if (config.getHttp() == null) {
throw new IllegalStateException("'http' config can't be null");
}

config.getHttp().validate();
if (config.getHttp().getAdminAccounts() == null) {
throw new IllegalStateException("'http.adminAccounts' config can't be null");
}

return config;
} catch (UnrecognizedPropertyException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
import java.util.List;

@Controller
@SuppressWarnings("unused")
public class DeploymentController {

public static final String UNKNOWN_ERROR = "UNKNOWN_ERROR";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import com.exactpro.th2.infrarepo.ResourceType;
import io.fabric8.kubernetes.client.ResourceNotFoundException;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
Expand All @@ -37,7 +38,6 @@
import static com.exactpro.th2.inframgr.statuswatcher.ResourcePath.annotationFor;

@Controller
@SuppressWarnings("unused")
public class DescriptorController {

private static final String PROTOBUF_DESCRIPTOR = "protobuf-description-base64";
Expand All @@ -50,6 +50,9 @@ public class DescriptorController {

private static final String UNKNOWN_ERROR = "UNKNOWN_ERROR";

@Autowired
private Config config;

@GetMapping("/descriptor/{schema}/{kind}/{box}")
@ResponseBody
public Response getDescriptor(@PathVariable(name = "schema") String schemaName,
Expand All @@ -70,7 +73,6 @@ public Response getDescriptor(@PathVariable(name = "schema") String schemaName,

String descriptor;
try {
Config config = Config.getInstance();
Kubernetes kube = new Kubernetes(config.getBehaviour(), config.getKubernetes(), schemaName);
RegistryCredentialLookup secretMapper = new RegistryCredentialLookup(kube);
RegistryConnection registryConnection = new RegistryConnection(secretMapper.getCredentials());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,17 @@

package com.exactpro.th2.inframgr;

import com.exactpro.th2.inframgr.metrics.PrometheusServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableScheduling;

import java.io.IOException;

@SpringBootApplication
@EnableScheduling
@EnableAutoConfiguration(exclude = {CassandraAutoConfiguration.class})
Expand All @@ -33,16 +35,16 @@ public class InfraManagerApplication {
public static void main(String[] args) {

try {
// preload configuration
Config.getInstance();

PrometheusServer.start();
SpringApplication application = new SpringApplication(InfraManagerApplication.class);
application.run(args);

} catch (Exception e) {
Logger logger = LoggerFactory.getLogger(InfraManagerApplication.class);
logger.error("Exiting with exception", e);
}
}

@Bean
public Config config() throws IOException {
return Config.instance();
}
}
9 changes: 6 additions & 3 deletions src/main/java/com/exactpro/th2/inframgr/JobController.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.eclipse.jgit.api.errors.GitAPIException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

Expand All @@ -36,7 +37,6 @@
import static com.exactpro.th2.inframgr.statuswatcher.ResourcePath.annotationFor;

@RestController
@SuppressWarnings("unused")
public class JobController {

private static final String UNKNOWN_ERROR = "UNKNOWN_ERROR";
Expand All @@ -49,6 +49,9 @@ public class JobController {

private static final Logger LOGGER = LoggerFactory.getLogger(JobController.class);

@Autowired
private Config config;

@PutMapping("/jobs/{schemaName}/{jobName}")
public void putSecrets(@PathVariable(name = "schemaName") String schemaName,
@PathVariable(name = "jobName") String jobName) {
Expand All @@ -61,11 +64,11 @@ public void putSecrets(@PathVariable(name = "schemaName") String schemaName,
}

try (Kubernetes kube = new Kubernetes(
Config.getInstance().getBehaviour(), Config.getInstance().getKubernetes(), schemaName
config.getBehaviour(), config.getKubernetes(), schemaName
)) {
RepositoryResource resource;
String resourceLabel;
GitterContext ctx = GitterContext.getContext(Config.getInstance().getGit());
GitterContext ctx = GitterContext.getContext(config.getGit());
Gitter gitter = ctx.getGitter(schemaName);
try {
gitter.lock();
Expand Down
Loading
Loading