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

Helm repo authentication #355

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;
import org.apache.commons.lang3.StringUtils;
import org.zeroturnaround.exec.InvalidExitValueException;

/** HelmExecuter */
Expand All @@ -28,7 +29,9 @@ public String addHelmRepo(
final String url,
final String nomRepo,
final boolean skipTlsVerify,
final String caFile)
final String caFile,
final String username,
final String password)
throws InvalidExitValueException, IOException, InterruptedException, TimeoutException {
String command = "helm repo add ";
if (skipTlsVerify) {
Expand All @@ -38,6 +41,12 @@ public String addHelmRepo(
command.concat(
"--ca-file " + System.getenv("CACERTS_DIR") + "/" + caFile + " ");
}
if (StringUtils.isNotEmpty(username)) {
command = command.concat("--username " + username + " ");
}
if (StringUtils.isNotEmpty(password)) {
command = command.concat("--password " + password + " ");
}
command = command.concat(nomRepo + " " + url);
return Command.execute(command).getOutput().getString();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class RepoServiceTest {
public void should() throws Exception {
HelmRepoService repoService = new HelmRepoService();
helmRepoService.addHelmRepo(
"https://inseefrlab.github.io/helm-charts", "inseefrlab", false, null);
"https://inseefrlab.github.io/helm-charts", "inseefrlab", false, null, null, null);
HelmRepo[] repos = helmRepoService.getHelmRepo();
Assertions.assertEquals(1, repos.length);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ public class CatalogWrapper {
@Schema(description = "Should this catalog be visible in user context ? Project context ?")
private CatalogVisibility visible = new CatalogVisibility();

@Schema(description = "Username for basic authentication")
private String username = null;

@Schema(description = "Password for basic authentication")
private String password = null;

/**
* @return the type
*/
Expand Down Expand Up @@ -195,6 +201,22 @@ public void setVisible(CatalogVisibility visible) {
this.visible = visible;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public static class CatalogVisibility {

private boolean user = true;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package fr.insee.onyxia.api.dao.universe;

import static java.nio.charset.StandardCharsets.UTF_8;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
Expand All @@ -12,18 +10,18 @@
import fr.insee.onyxia.model.helm.Repository;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.List;
import java.util.stream.Collectors;
import okhttp3.Credentials;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
Expand All @@ -39,6 +37,8 @@ public class CatalogLoader {
@Qualifier("helm")
private ObjectMapper mapperHelm;

@Autowired private OkHttpClient httpClient;

public void updateCatalog(CatalogWrapper cw) {
LOGGER.info("updating catalog with id :{} and type {}", cw.getId(), cw.getType());
if (cw.getType().equals(Repository.TYPE_HELM)) {
Expand All @@ -50,14 +50,11 @@ public void updateCatalog(CatalogWrapper cw) {

/** TODO : move this helm specific logic somewhere else ? */
private void updateHelmRepository(CatalogWrapper cw) {
try {
Reader reader =
new InputStreamReader(
resourceLoader
.getResource(cw.getLocation() + "/index.yaml")
.getInputStream(),
UTF_8);
Repository repository = mapperHelm.readValue(reader, Repository.class);
try (InputStream stream =
fetchResource(
cw.getLocation() + "/index.yaml", cw.getUsername(), cw.getPassword())) {
Repository repository = mapperHelm.readValue(stream, Repository.class);

repository
.getEntries()
.entrySet()
Expand Down Expand Up @@ -113,13 +110,25 @@ private void refreshPackage(CatalogWrapper cw, Pkg pkg)
absoluteUrl = StringUtils.applyRelativePath(cw.getLocation() + "/", chartUrl);
}

Resource resource = resourceLoader.getResource(absoluteUrl);

try (InputStream inputStream = resource.getInputStream()) {
try (InputStream inputStream =
fetchResource(absoluteUrl, cw.getUsername(), cw.getPassword())) {
extractDataFromTgz(inputStream, chart);
} catch (IOException e) {
throw new CatalogLoaderException(
"Exception occurred during loading resource: " + resource.getDescription(), e);
"Exception occurred during loading resource: " + absoluteUrl, e);
}
}

private InputStream fetchResource(String url, String username, String password)
throws IOException {
if (url.startsWith("http")) {
Request.Builder builder = new Request.Builder().url(url);
if (username != null && password != null) {
builder = builder.addHeader("Authorization", Credentials.basic(username, password));
}
return httpClient.newCall(builder.build()).execute().body().byteStream();
} else {
return resourceLoader.getResource(url).getInputStream();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ private void refresh() {
c.getLocation(),
c.getId(),
c.getSkipTlsVerify(),
c.getCaFile()));
c.getCaFile(),
c.getUsername(),
c.getPassword()));
catalogLoader.updateCatalog(c);
} catch (Exception e) {
LOGGER.warn("Exception occurred", e);
Expand Down
12 changes: 9 additions & 3 deletions onyxia-api/src/main/resources/catalogs.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
"visible": {
"user": true,
"project": true
}
},
"username": null,
"password": null
},
{
"id": "databases",
Expand All @@ -32,7 +34,9 @@
"visible": {
"user": true,
"project": true
}
},
"username": null,
"password": null
},
{
"id": "automation",
Expand All @@ -49,7 +53,9 @@
"visible": {
"user": true,
"project": true
}
},
"username": null,
"password": null
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import fr.insee.onyxia.api.configuration.CatalogWrapper;
import fr.insee.onyxia.api.configuration.CustomObjectMapper;
import fr.insee.onyxia.api.configuration.HttpClientProvider;
import fr.insee.onyxia.api.util.TestUtils;
import fr.insee.onyxia.model.helm.Chart;
import java.util.List;
Expand All @@ -15,18 +16,15 @@
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.io.ResourceLoader;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.util.CollectionUtils;

@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = {CatalogLoader.class, CustomObjectMapper.class})
@SpringBootTest(classes = {CatalogLoader.class, CustomObjectMapper.class, HttpClientProvider.class})
public class CatalogLoaderTest {

@Autowired CatalogLoader catalogLoader;

@Autowired ResourceLoader resourceLoader;

@DisplayName(
"Given a helm catalog wrapper with local charts and excluded charts, "
+ "when we update the catalog, "
Expand Down Expand Up @@ -88,12 +86,10 @@ public void packageOnClassPathNotFound() {
catalogLoader.updateCatalog(cw);

String stdErrLogs = TestUtils.tapSystemOut(() -> catalogLoader.updateCatalog(cw));

assertThat(
stdErrLogs,
containsString(
"fr.insee.onyxia.api.dao.universe.CatalogLoaderException: "
+ "Exception occurred during loading resource: class path resource "
+ "[catalog-loader-test/keepeme1.gz]"));
+ "Exception occurred during loading resource: classpath"));
}
}