Skip to content

Commit

Permalink
Merge pull request #728 from at88mph/private-harbor-rework
Browse files Browse the repository at this point in the history
Private harbor rework
  • Loading branch information
at88mph authored Nov 14, 2024
2 parents f94016c + 6d34d73 commit a832399
Show file tree
Hide file tree
Showing 18 changed files with 237 additions and 44 deletions.
4 changes: 2 additions & 2 deletions deployment/helm/science-portal/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.3.0
version: 0.3.1

# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "0.3.0"
appVersion: "0.3.1"

dependencies:
- name: "redis"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ org.opencadc.science-portal.oidc.clientSecret = {{ $clientSecret }}
org.opencadc.science-portal.oidc.clientSecret = {{ .clientSecret }}
{{- end }}

org.opencadc.science-portal.oidc.clientSecret = {{ .clientSecret }}
org.opencadc.science-portal.oidc.callbackURI = {{ .callbackURI }}
org.opencadc.science-portal.oidc.redirectURI = {{ .redirectURI }}
org.opencadc.science-portal.oidc.scope = {{ .scope }}
Expand Down
6 changes: 5 additions & 1 deletion deployment/helm/science-portal/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@ skaha:
deployment:
hostname: example.host.com # Change this!
sciencePortal:
image: images.opencadc.org/platform/science-portal:0.3.0
image: images.opencadc.org/platform/science-portal:0.3.1
imagePullPolicy: Always

tabLabels:
- "Standard"
- "Advanced"

# Optionally set the DEBUG port.
# extraEnv:
# - name: CATALINA_OPTS
Expand Down
4 changes: 2 additions & 2 deletions deployment/helm/skaha/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.9.0
version: 0.9.1

# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "0.23.0"
appVersion: "0.23.1"

dependencies:
- name: "redis"
Expand Down
2 changes: 1 addition & 1 deletion deployment/helm/skaha/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ skahaWorkload:
deployment:
hostname: myhost.example.com # Change this!
skaha:
image: images.opencadc.org/platform/skaha:0.23.0
image: images.opencadc.org/platform/skaha:0.23.1
imagePullPolicy: Always

# Cron string for the image caching cron job schedule. Defaults to every minute.
Expand Down
2 changes: 1 addition & 1 deletion skaha/VERSION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
## deployable containers have a semantic and build tag
# version tag: major.minor.patch
# build version tag: timestamp
VER=0.23.0
VER=0.23.1
TAGS="${VER} ${VER}-$(date -u +"%Y%m%dT%H%M%S")"
unset VER
74 changes: 74 additions & 0 deletions skaha/src/intTest/java/org/opencadc/skaha/RepositoryHostsTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package org.opencadc.skaha;

import ca.nrc.cadc.auth.AuthMethod;
import ca.nrc.cadc.net.HttpGet;
import ca.nrc.cadc.reg.Standards;
import ca.nrc.cadc.reg.client.RegistryClient;
import ca.nrc.cadc.util.Log4jInit;
import ca.nrc.cadc.util.StringUtil;
import java.io.ByteArrayOutputStream;
import java.net.URL;
import java.security.PrivilegedExceptionAction;
import java.util.Arrays;
import javax.security.auth.Subject;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.json.JSONArray;
import org.junit.Assert;
import org.junit.Test;

public class RepositoryHostsTest {
private static final Logger log = Logger.getLogger(RepositoryHostsTest.class);

static {
Log4jInit.setLevel("org.opencadc.skaha", Level.INFO);
}

protected final URL repositoryURL;
protected final Subject userSubject;

public RepositoryHostsTest() throws Exception {
final String configuredServiceEndpoint = System.getProperty("SKAHA_SERVICE_ENDPOINT");
if (StringUtil.hasText(configuredServiceEndpoint)) {
repositoryURL = new URL(configuredServiceEndpoint);
} else {
final RegistryClient regClient = new RegistryClient();
final URL imageServiceURL = regClient.getServiceURL(
SessionUtil.getSkahaServiceID(), Standards.PROC_SESSIONS_10, AuthMethod.TOKEN);
repositoryURL = new URL(imageServiceURL.toExternalForm() + "/repository");
}
log.info("sessions URL: " + repositoryURL);

this.userSubject = SessionUtil.getCurrentUser(repositoryURL, false);
}

protected static String[] getRepositoryHosts(final URL serviceURLEndpoint) {
final ByteArrayOutputStream out = new ByteArrayOutputStream();
final HttpGet get = new HttpGet(serviceURLEndpoint, out);
get.run();
Assert.assertNull("get repository hosts error", get.getThrowable());
Assert.assertEquals("response code", 200, get.getResponseCode());
Assert.assertEquals("content-type", "application/json", get.getContentType());
final JSONArray jsonArray = new JSONArray(out.toString());
return jsonArray.toList().stream().map(Object::toString).toArray(String[]::new);
}

@Test
public void testGetImageList() {
try {
Subject.doAs(this.userSubject, (PrivilegedExceptionAction<Object>) () -> {
// should have at least one image
final String[] repositoryHosts = RepositoryHostsTest.getRepositoryHosts(this.repositoryURL);
Assert.assertNotEquals(
"Should have at least one (" + Arrays.toString(repositoryHosts) + ")",
0,
repositoryHosts.length);
return null;
});

} catch (Exception t) {
log.error("unexpected throwable", t);
Assert.fail("unexpected throwable: " + t);
}
}
}
6 changes: 3 additions & 3 deletions skaha/src/main/java/org/opencadc/skaha/SkahaAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@
import org.opencadc.permissions.TokenTool;
import org.opencadc.permissions.WriteGrant;
import org.opencadc.skaha.image.Image;
import org.opencadc.skaha.registry.ImageRegistryAuth;
import org.opencadc.skaha.repository.ImageRepositoryAuth;
import org.opencadc.skaha.session.Session;
import org.opencadc.skaha.session.SessionDAO;
import org.opencadc.skaha.utils.CommandExecutioner;
Expand Down Expand Up @@ -280,13 +280,13 @@ protected void initRequest() throws Exception {
}
}

protected ImageRegistryAuth getRegistryAuth(final String registryHost) {
protected ImageRepositoryAuth getRegistryAuth(final String registryHost) {
final String registryAuthValue = this.syncInput.getHeader(SkahaAction.X_REGISTRY_AUTH_HEADER);
if (!StringUtil.hasText(registryAuthValue)) {
throw new IllegalArgumentException("No authentication provided for unknown or private image. Use "
+ SkahaAction.X_REGISTRY_AUTH_HEADER + " request header with base64Encode(username:secret).");
}
return ImageRegistryAuth.fromEncoded(registryAuthValue, registryHost);
return ImageRepositoryAuth.fromEncoded(registryAuthValue, registryHost);
}

private boolean isSkahaCallBackFlow(Subject currentSubject) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public void doAction() throws Exception {
super.initRequest();

File propertiesFile = ResourceContexts.getResourcesFile("k8s-resources.json");
byte[] bytes = getBytes(propertiesFile);
byte[] bytes = GetAction.getBytes(propertiesFile);
syncOutput.setHeader("Content-Type", "application/json");
syncOutput.getOutputStream().write(bytes);
}
Expand Down
43 changes: 43 additions & 0 deletions skaha/src/main/java/org/opencadc/skaha/repository/GetAction.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.opencadc.skaha.repository;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.Arrays;
import org.json.JSONWriter;
import org.opencadc.skaha.SkahaAction;

/**
* Output the resource context information.
*
* @author majorb
*/
public class GetAction extends SkahaAction {

@Override
public void doAction() throws Exception {
initRequest();
final Writer writer = initWriter();
final JSONWriter jsonWriter = new JSONWriter(writer).array();
try {
Arrays.stream(getHarborHosts()).forEach(jsonWriter::value);
} finally {
jsonWriter.endArray();
writer.flush();
}
}

@Override
protected void initRequest() throws Exception {
super.initRequest();
}

Writer initWriter() throws IOException {
this.syncOutput.setHeader("content-type", "application/json");
return new OutputStreamWriter(syncOutput.getOutputStream());
}

String[] getHarborHosts() {
return this.harborHosts.toArray(new String[0]);
}
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
package org.opencadc.skaha.registry;
package org.opencadc.skaha.repository;

import ca.nrc.cadc.util.Base64;
import ca.nrc.cadc.util.StringUtil;
import java.nio.charset.StandardCharsets;

/**
* Represents credentials to an image registry. Will be used from the request input as a base64 encoded value.
* Represents credentials to an image repository. Will be used from the request input as a base64 encoded value.
*/
public class ImageRegistryAuth {
public class ImageRepositoryAuth {
private static final String VALUE_DELIMITER = ":";

private final String host;
private final String username;
private final byte[] secret;

ImageRegistryAuth(final String username, final byte[] secret, final String host) {
ImageRepositoryAuth(final String username, final byte[] secret, final String host) {
if (!StringUtil.hasText(username)) {
throw new IllegalArgumentException("username is required.");
} else if (secret.length == 0) {
throw new IllegalArgumentException("secret value cannot be empty.");
} else if (!StringUtil.hasText(host)) {
throw new IllegalArgumentException("registry host is required.");
throw new IllegalArgumentException("repository host is required.");
}

this.username = username;
Expand All @@ -29,31 +29,31 @@ public class ImageRegistryAuth {
}

/**
* Constructor to use the Base64 encoded value to obtain the credentials for an Image Registry.
* Constructor to use the Base64 encoded value to obtain the credentials for an Image Repository.
*
* @param encodedValue The Base64 encoded String. Never null.
* @param host The registry host for these credentials.
* @return ImageRegistryAuth instance. Never null.
* @param host The repository host for these credentials.
* @return ImageRepositoryAuth instance. Never null.
*/
public static ImageRegistryAuth fromEncoded(final String encodedValue, final String host) {
public static ImageRepositoryAuth fromEncoded(final String encodedValue, final String host) {
if (!StringUtil.hasText(encodedValue)) {
throw new IllegalArgumentException("Encoded auth username and key is required.");
} else if (!StringUtil.hasText(host)) {
throw new IllegalArgumentException("Registry host is required.");
throw new IllegalArgumentException("Repository host is required.");
}

final String decodedValue = new String(Base64.decode(encodedValue), StandardCharsets.UTF_8);
final String[] values = decodedValue.split(ImageRegistryAuth.VALUE_DELIMITER);
final String[] values = decodedValue.split(ImageRepositoryAuth.VALUE_DELIMITER);

if (values.length != 2) {
throw new IllegalArgumentException("Invalid input. Must be in form of username:secret");
}

return new ImageRegistryAuth(values[0].trim(), values[1].trim().getBytes(), host);
return new ImageRepositoryAuth(values[0].trim(), values[1].trim().getBytes(), host);
}

public String getEncoded() {
return Base64.encodeString(this.username + ImageRegistryAuth.VALUE_DELIMITER + new String(this.secret));
return Base64.encodeString(this.username + ImageRepositoryAuth.VALUE_DELIMITER + new String(this.secret));
}

public String getHost() {
Expand Down
10 changes: 5 additions & 5 deletions skaha/src/main/java/org/opencadc/skaha/session/PostAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@
import org.opencadc.skaha.SkahaAction;
import org.opencadc.skaha.context.ResourceContexts;
import org.opencadc.skaha.image.Image;
import org.opencadc.skaha.registry.ImageRegistryAuth;
import org.opencadc.skaha.repository.ImageRepositoryAuth;
import org.opencadc.skaha.utils.CommandExecutioner;
import org.opencadc.skaha.utils.PosixCache;

Expand Down Expand Up @@ -503,8 +503,8 @@ private String validateImage(String imageID, String type) throws Exception {
// TODO: and since we can't verify one way or the other, let them through.
if (image == null) {
log.warn("Image " + imageID + " missing from cache...");
final ImageRegistryAuth imageRegistryAuth = getRegistryAuth(imageRegistryHost);
if (imageRegistryAuth == null) {
final ImageRepositoryAuth imageRepositoryAuth = getRegistryAuth(imageRegistryHost);
if (imageRepositoryAuth == null) {
throw new ResourceNotFoundException("image not found or not labelled: " + imageID);
} else {
log.warn("Assuming image " + imageID + " is private as credentials were supplied.");
Expand Down Expand Up @@ -606,7 +606,7 @@ public void createSession(
// In the absence of the existence of a public image, assume Private. The validateImage() step above will have
// caught a non-existent Image already.
if (getPublicImage(image) == null) {
final ImageRegistryAuth userRegistryAuth = getRegistryAuth(getRegistryHost(image));
final ImageRepositoryAuth userRegistryAuth = getRegistryAuth(getRegistryHost(image));
imageRegistrySecretName = createRegistryImageSecret(userRegistryAuth);
} else {
imageRegistrySecretName = PostAction.DEFAULT_SOFTWARE_IMAGESECRET_VALUE;
Expand Down Expand Up @@ -697,7 +697,7 @@ private String generateToken() throws Exception {
* @param registryAuth The credentials to use to authenticate to the Image Registry.
* @return String secret name, never null.
*/
private String createRegistryImageSecret(final ImageRegistryAuth registryAuth) throws Exception {
private String createRegistryImageSecret(final ImageRepositoryAuth registryAuth) throws Exception {
final String username = this.posixPrincipal.username;
final String secretName = "registry-auth-" + username.toLowerCase();
log.debug("Creating user secret " + secretName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import org.apache.log4j.Logger;
import org.json.JSONObject;
import org.opencadc.skaha.K8SUtil;
import org.opencadc.skaha.registry.ImageRegistryAuth;
import org.opencadc.skaha.repository.ImageRepositoryAuth;

public class CommandExecutioner {
private static final Logger log = Logger.getLogger(CommandExecutioner.class);
Expand Down Expand Up @@ -92,7 +92,7 @@ public static void execute(final String[] command, final OutputStream standardOu
* @param secretName The name of the secret to create.
* @throws Exception If there is an error creating the secret.
*/
public static void ensureRegistrySecret(final ImageRegistryAuth registryAuth, final String secretName)
public static void ensureRegistrySecret(final ImageRepositoryAuth registryAuth, final String secretName)
throws Exception {
// delete any old secret by this name
final String[] deleteCmd = CommandExecutioner.getDeleteSecretCommand(secretName);
Expand Down Expand Up @@ -135,7 +135,7 @@ static String[] getDeleteSecretCommand(final String secretName) {
return new String[] {"kubectl", "--namespace", K8SUtil.getWorkloadNamespace(), "delete", "secret", secretName};
}

static String[] getRegistryCreateSecretCommand(final ImageRegistryAuth registryAuth, final String secretName) {
static String[] getRegistryCreateSecretCommand(final ImageRepositoryAuth registryAuth, final String secretName) {
if (registryAuth == null) {
throw new IllegalArgumentException("registryAuth is required.");
} else if (!StringUtil.hasText(secretName)) {
Expand Down
15 changes: 15 additions & 0 deletions skaha/src/main/webapp/WEB-INF/web.xml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,16 @@
<load-on-startup>2</load-on-startup>
</servlet>

<servlet>
<servlet-name>ImageRepositoryServlet</servlet-name>
<servlet-class>ca.nrc.cadc.rest.RestServlet</servlet-class>
<init-param>
<param-name>get</param-name>
<param-value>org.opencadc.skaha.repository.GetAction</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>

<!-- Servlet that identifies the services provided by platform -->
<servlet>
<servlet-name>CapabilitiesServlet</servlet-name>
Expand Down Expand Up @@ -122,6 +132,11 @@
<servlet-name>ImageServlet</servlet-name>
<url-pattern>/v0/image/*</url-pattern>
</servlet-mapping>

<servlet-mapping>
<servlet-name>ImageRepositoryServlet</servlet-name>
<url-pattern>/v0/repository/*</url-pattern>
</servlet-mapping>

<servlet-mapping>
<servlet-name>CapabilitiesServlet</servlet-name>
Expand Down
Loading

0 comments on commit a832399

Please sign in to comment.