fix(ci): authenticate releases through scoped credential helper
This commit is contained in:
1 parent
7a481f366c
commit
9d3791a9c3
4 files changed
+75
-14
No files matched your search
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/bin/sh
|
||||
# Git credential protocol; never invoke directly with a real token in recorded output.
|
||||
# Only Git receives credential stdout. No secret is stored in this file or passed in argv.
|
||||
set -eu
|
||||
[ "${1:-}" = get ] || exit 0
|
||||
protocol='' host='' path=''
|
||||
while IFS='=' read -r key value && [ -n "$key" ]; do
|
||||
case "$key" in
|
||||
protocol) protocol=$value ;;
|
||||
host) host=$value ;;
|
||||
path) path=$value ;;
|
||||
esac
|
||||
done
|
||||
[ "$protocol" = https ] || exit 0
|
||||
[ "$host" = git.garvis.dev ] || exit 0
|
||||
[ "$path" = dmg/spigot-ignis.git ] || exit 0
|
||||
[ -n "${IGNIS_RELEASE_TOKEN:-}" ] || exit 0
|
||||
printf 'username=dmg\npassword=%s\n\n' "$IGNIS_RELEASE_TOKEN"
|
||||
@@ -48,20 +48,13 @@ jobs:
|
||||
test -n "$IGNIS_RELEASE_TOKEN" || { echo 'RELEASE_TOKEN is required'; exit 1; }
|
||||
test "$GITEA_SERVER_URL" = 'https://git.garvis.dev'
|
||||
test "$REPOSITORY" = 'dmg/spigot-ignis'
|
||||
# Token stays in environment/stdout to Git's protected askpass pipe, not a URL or argv.
|
||||
askpass=$(mktemp)
|
||||
trap 'rm -f "$askpass"' EXIT
|
||||
chmod 700 "$askpass"
|
||||
cat > "$askpass" <<'SH'
|
||||
#!/bin/sh
|
||||
case "$1" in
|
||||
*Username*) printf '%s\n' 'x-access-token' ;;
|
||||
*Password*) printf '%s\n' "$IGNIS_RELEASE_TOKEN" ;;
|
||||
*) exit 1 ;;
|
||||
esac
|
||||
SH
|
||||
export GIT_ASKPASS="$askpass" GIT_TERMINAL_PROMPT=0
|
||||
# semantic-release overrides GIT_ASKPASS; use Git's credential protocol instead.
|
||||
# Token stays in environment/credential stdout, never in the remote URL or argv.
|
||||
export GIT_TERMINAL_PROMPT=0
|
||||
git config credential.helper ''
|
||||
git config --add credential.helper "$PWD/.gitea/scripts/git-credential-ignis.sh"
|
||||
git config credential.useHttpPath true
|
||||
trap 'git config --unset-all credential.helper || true; git config --unset credential.useHttpPath || true' EXIT
|
||||
git remote set-url origin "${GITEA_SERVER_URL}/${REPOSITORY}.git"
|
||||
previous=$(git describe --tags --abbrev=0 2>/dev/null || true)
|
||||
npx -y \
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package games.dmg.spigotignis;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class ReleaseCredentialTest {
|
||||
@Test
|
||||
void credentialHelperRefusesOtherHostsProtocolsRepositoriesAndOperations() throws Exception {
|
||||
Path helper = Path.of(".gitea/scripts/git-credential-ignis.sh").toAbsolutePath();
|
||||
for (String request : java.util.List.of(
|
||||
"protocol=https\nhost=evil.example\npath=dmg/spigot-ignis.git\n\n",
|
||||
"protocol=http\nhost=git.garvis.dev\npath=dmg/spigot-ignis.git\n\n",
|
||||
"protocol=https\nhost=git.garvis.dev\npath=dmg/other.git\n\n")) {
|
||||
var builder = new ProcessBuilder("sh", helper.toString(), "get");
|
||||
builder.environment().put("IGNIS_RELEASE_TOKEN", "synthetic-test-token");
|
||||
var process = builder.start();
|
||||
try (var input = process.getOutputStream()) { input.write(request.getBytes(StandardCharsets.UTF_8)); }
|
||||
assertTrue(process.waitFor(3, TimeUnit.SECONDS));
|
||||
assertEquals("", new String(process.getInputStream().readAllBytes(), StandardCharsets.UTF_8));
|
||||
}
|
||||
var store = new ProcessBuilder("sh", helper.toString(), "store").start();
|
||||
store.getOutputStream().close();
|
||||
assertTrue(store.waitFor(3, TimeUnit.SECONDS));
|
||||
assertEquals("", new String(store.getInputStream().readAllBytes(), StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@Test
|
||||
void scopedHelperWorksEvenWhenSemanticReleaseOverridesAskpass() throws Exception {
|
||||
Path helper = Path.of(".gitea/scripts/git-credential-ignis.sh").toAbsolutePath();
|
||||
assertTrue(java.nio.file.Files.exists(helper), "A credential helper must survive semantic-release's askpass override");
|
||||
var builder = new ProcessBuilder("git", "-c", "credential.helper=", "-c", "credential.helper=" + helper,
|
||||
"-c", "credential.useHttpPath=true", "credential", "fill");
|
||||
builder.environment().put("IGNIS_RELEASE_TOKEN", "synthetic-test-token");
|
||||
builder.environment().put("GIT_ASKPASS", "echo");
|
||||
builder.environment().put("GIT_TERMINAL_PROMPT", "0");
|
||||
Process process = builder.start();
|
||||
try (var input = process.getOutputStream()) {
|
||||
input.write("protocol=https\nhost=git.garvis.dev\npath=dmg/spigot-ignis.git\n\n".getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
assertTrue(process.waitFor(3, TimeUnit.SECONDS));
|
||||
assertEquals(0, process.exitValue());
|
||||
String result = new String(process.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
|
||||
assertTrue(result.contains("username=dmg"));
|
||||
assertTrue(result.contains("password=synthetic-test-token"));
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,8 @@ class ReleaseWorkflowTest {
|
||||
String workflow = Files.readString(path);
|
||||
assertTrue(workflow.contains("spigot-ignis-${VERSION}.jar"));
|
||||
assertTrue(workflow.contains("--header @-"));
|
||||
assertTrue(workflow.contains("GIT_ASKPASS"));
|
||||
assertTrue(workflow.contains("git-credential-ignis.sh"));
|
||||
assertTrue(workflow.contains("credential.useHttpPath true"));
|
||||
assertTrue(workflow.contains("persist-credentials: false"));
|
||||
assertFalse(workflow.contains("-H \"Authorization:"));
|
||||
assertFalse(workflow.contains("GITHUB_TOKEN:"), "Do not pass a token for semantic-release to embed into git URLs");
|
||||
|
||||
Reference in new issue
Block a user