Skip to content

Commit 4175018

Browse files
Yanniccopybara-github
authored andcommitted
Add util for finding credential helper to use
Progress on https://github.com/bazelbuild/proposals/blob/main/designs/2022-06-07-bazel-credential-helpers.md Closes bazelbuild#15707. PiperOrigin-RevId: 458456496 Change-Id: I751a594144c3563096ee9794c41329b49755824e
1 parent 255e8f5 commit 4175018

File tree

7 files changed

+665
-2
lines changed

7 files changed

+665
-2
lines changed

src/main/java/com/google/devtools/build/lib/authandtls/BUILD

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ package(default_visibility = ["//src:__subpackages__"])
44

55
filegroup(
66
name = "srcs",
7-
srcs = glob(["**"]),
7+
srcs = glob(["**"]) + [
8+
"//src/main/java/com/google/devtools/build/lib/authandtls/credentialhelper:srcs",
9+
],
810
visibility = ["//src:__subpackages__"],
911
)
1012

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
load("@rules_java//java:defs.bzl", "java_library")
2+
3+
package(default_visibility = ["//src:__subpackages__"])
4+
5+
licenses(["notice"])
6+
7+
filegroup(
8+
name = "srcs",
9+
srcs = glob(["**"]),
10+
visibility = ["//src:__subpackages__"],
11+
)
12+
13+
java_library(
14+
name = "credentialhelper",
15+
srcs = glob(["*.java"]),
16+
deps = [
17+
"//src/main/java/com/google/devtools/build/lib/vfs",
18+
"//third_party:error_prone_annotations",
19+
"//third_party:guava",
20+
],
21+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright 2022 The Bazel Authors. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.devtools.build.lib.authandtls.credentialhelper;
16+
17+
import com.google.common.annotations.VisibleForTesting;
18+
import com.google.common.base.Preconditions;
19+
import com.google.devtools.build.lib.vfs.Path;
20+
import com.google.errorprone.annotations.Immutable;
21+
22+
/** Wraps an external tool used to obtain credentials. */
23+
@Immutable
24+
public final class CredentialHelper {
25+
// `Path` is immutable, but not annotated.
26+
@SuppressWarnings("Immutable")
27+
private final Path path;
28+
29+
CredentialHelper(Path path) {
30+
this.path = Preconditions.checkNotNull(path);
31+
}
32+
33+
@VisibleForTesting
34+
Path getPath() {
35+
return path;
36+
}
37+
38+
// TODO(yannic): Implement running the helper subprocess.
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
// Copyright 2022 The Bazel Authors. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.devtools.build.lib.authandtls.credentialhelper;
16+
17+
import com.google.common.annotations.VisibleForTesting;
18+
import com.google.common.base.Preconditions;
19+
import com.google.common.collect.ImmutableMap;
20+
import com.google.devtools.build.lib.vfs.Path;
21+
import com.google.errorprone.annotations.Immutable;
22+
import java.io.IOException;
23+
import java.net.IDN;
24+
import java.net.URI;
25+
import java.util.HashMap;
26+
import java.util.Locale;
27+
import java.util.Map;
28+
import java.util.Optional;
29+
import java.util.regex.Pattern;
30+
31+
/**
32+
* A provider for {@link CredentialHelper}s.
33+
*
34+
* <p>This class is used to find the right {@link CredentialHelper} for a {@link URI}, using the
35+
* most specific match.
36+
*/
37+
@Immutable
38+
public final class CredentialHelperProvider {
39+
// `Path` is immutable, but not annotated.
40+
@SuppressWarnings("Immutable")
41+
private final Optional<Path> defaultHelper;
42+
43+
@SuppressWarnings("Immutable")
44+
private final ImmutableMap<String, Path> hostToHelper;
45+
46+
@SuppressWarnings("Immutable")
47+
private final ImmutableMap<String, Path> suffixToHelper;
48+
49+
private CredentialHelperProvider(
50+
Optional<Path> defaultHelper,
51+
ImmutableMap<String, Path> hostToHelper,
52+
ImmutableMap<String, Path> suffixToHelper) {
53+
this.defaultHelper = Preconditions.checkNotNull(defaultHelper);
54+
this.hostToHelper = Preconditions.checkNotNull(hostToHelper);
55+
this.suffixToHelper = Preconditions.checkNotNull(suffixToHelper);
56+
}
57+
58+
/**
59+
* Returns {@link CredentialHelper} to use for getting credentials for connection to the provided
60+
* {@link URI}.
61+
*
62+
* @param uri The {@link URI} to get a credential helper for.
63+
* @return The {@link CredentialHelper}, or nothing if no {@link CredentialHelper} is configured
64+
* for the provided {@link URI}.
65+
*/
66+
public Optional<CredentialHelper> findCredentialHelper(URI uri) {
67+
Preconditions.checkNotNull(uri);
68+
69+
String host = Preconditions.checkNotNull(uri.getHost());
70+
Optional<Path> credentialHelper =
71+
findHostCredentialHelper(host)
72+
.or(() -> findWildcardCredentialHelper(host))
73+
.or(() -> defaultHelper);
74+
return credentialHelper.map(CredentialHelper::new);
75+
}
76+
77+
private Optional<Path> findHostCredentialHelper(String host) {
78+
Preconditions.checkNotNull(host);
79+
80+
return Optional.ofNullable(hostToHelper.get(host));
81+
}
82+
83+
private Optional<Path> findWildcardCredentialHelper(String host) {
84+
Preconditions.checkNotNull(host);
85+
86+
return Optional.ofNullable(suffixToHelper.get(host))
87+
.or(
88+
() -> {
89+
Optional<String> subdomain = parentDomain(host);
90+
if (subdomain.isEmpty()) {
91+
return Optional.empty();
92+
}
93+
return findWildcardCredentialHelper(subdomain.get());
94+
});
95+
}
96+
97+
/**
98+
* Returns the parent domain of the provided domain (e.g., {@code foo.example.com} for {@code
99+
* bar.foo.example.com}).
100+
*/
101+
@VisibleForTesting
102+
static Optional<String> parentDomain(String domain) {
103+
int dot = domain.indexOf('.');
104+
if (dot < 0) {
105+
// We reached the last segment, end.
106+
return Optional.empty();
107+
}
108+
109+
return Optional.of(domain.substring(dot + 1));
110+
}
111+
112+
/** Returns a new builder for a {@link CredentialHelperProvider}. */
113+
public static Builder builder() {
114+
return new Builder();
115+
}
116+
117+
/** Builder for {@link CredentialHelperProvider}. */
118+
public static final class Builder {
119+
private static final Pattern DOMAIN_PATTERN =
120+
Pattern.compile("(\\*|[-a-zA-Z0-9]+)(\\.[-a-zA-Z0-9]+)+");
121+
122+
private Optional<Path> defaultHelper = Optional.empty();
123+
private final Map<String, Path> hostToHelper = new HashMap<>();
124+
private final Map<String, Path> suffixToHelper = new HashMap<>();
125+
126+
private void checkHelper(Path path) throws IOException {
127+
Preconditions.checkNotNull(path);
128+
Preconditions.checkArgument(
129+
path.isExecutable(), "Credential helper %s is not executable", path);
130+
}
131+
132+
/**
133+
* Adds a default credential helper to use for all {@link URI}s that don't specify a more
134+
* specific credential helper.
135+
*/
136+
public Builder add(Path helper) throws IOException {
137+
checkHelper(helper);
138+
139+
defaultHelper = Optional.of(helper);
140+
return this;
141+
}
142+
143+
/**
144+
* Adds a credential helper to use for all {@link URI}s matching the provided pattern.
145+
*
146+
* <p>As of 2022-06-20, only matching based on (wildcard) domain name is supported.
147+
*
148+
* <p>If {@code pattern} starts with {@code *.}, it is considered a wildcard pattern matching
149+
* all subdomains in addition to the domain itself. For example {@code *.example.com} would
150+
* match {@code example.com}, {@code foo.example.com}, {@code bar.example.com}, {@code
151+
* baz.bar.example.com} and so on, but not anything that isn't a subdomain of {@code
152+
* example.com}.
153+
*/
154+
public Builder add(String pattern, Path helper) throws IOException {
155+
Preconditions.checkNotNull(pattern);
156+
checkHelper(helper);
157+
158+
String punycodePattern = toPunycodePattern(pattern);
159+
Preconditions.checkArgument(
160+
DOMAIN_PATTERN.matcher(punycodePattern).matches(),
161+
"Pattern '%s' is not a valid (wildcard) DNS name",
162+
pattern);
163+
164+
if (pattern.startsWith("*.")) {
165+
suffixToHelper.put(punycodePattern.substring(2), helper);
166+
} else {
167+
hostToHelper.put(punycodePattern, helper);
168+
}
169+
170+
return this;
171+
}
172+
173+
/** Converts a pattern to Punycode (see https://en.wikipedia.org/wiki/Punycode). */
174+
private final String toPunycodePattern(String pattern) {
175+
Preconditions.checkNotNull(pattern);
176+
177+
try {
178+
return IDN.toASCII(pattern);
179+
} catch (IllegalArgumentException e) {
180+
throw new IllegalArgumentException(
181+
String.format(Locale.US, "Could not convert '%s' to punycode", pattern), e);
182+
}
183+
}
184+
185+
public CredentialHelperProvider build() {
186+
return new CredentialHelperProvider(
187+
defaultHelper, ImmutableMap.copyOf(hostToHelper), ImmutableMap.copyOf(suffixToHelper));
188+
}
189+
}
190+
}

src/test/java/com/google/devtools/build/lib/authandtls/BUILD

+3-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ licenses(["notice"])
1010
filegroup(
1111
name = "srcs",
1212
testonly = 0,
13-
srcs = glob(["**"]),
13+
srcs = glob(["**"]) + [
14+
"//src/test/java/com/google/devtools/build/lib/authandtls/credentialhelper:srcs",
15+
],
1416
visibility = ["//src:__subpackages__"],
1517
)
1618

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
load("@rules_java//java:defs.bzl", "java_test")
2+
3+
package(
4+
default_testonly = 1,
5+
default_visibility = ["//src:__subpackages__"],
6+
)
7+
8+
licenses(["notice"])
9+
10+
filegroup(
11+
name = "srcs",
12+
testonly = 0,
13+
srcs = glob(["**"]),
14+
visibility = ["//src:__subpackages__"],
15+
)
16+
17+
java_test(
18+
name = "credentialhelper",
19+
srcs = glob(["*.java"]),
20+
test_class = "com.google.devtools.build.lib.AllTests",
21+
runtime_deps = [
22+
"//src/test/java/com/google/devtools/build/lib:test_runner",
23+
],
24+
deps = [
25+
"//src/main/java/com/google/devtools/build/lib/authandtls/credentialhelper",
26+
"//src/main/java/com/google/devtools/build/lib/vfs",
27+
"//src/main/java/com/google/devtools/build/lib/vfs:pathfragment",
28+
"//src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs",
29+
"//third_party:guava",
30+
"//third_party:junit4",
31+
"//third_party:truth",
32+
],
33+
)

0 commit comments

Comments
 (0)