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

feat(baseuri): add baseuri-range config property to control servers the Agent will register with #243

Merged
merged 1 commit into from
Nov 6, 2023
Merged
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ Below is a list of configuration properties that can be used to influence how `c
and how it advertises itself to a Cryostat server instance. Required properties are indicated with a checked box.

- [x] `cryostat.agent.baseuri` [`java.net.URI`]: the URL location of the Cryostat server backend that this agent advertises itself to.
- [x] `cryostat.agent.baseuri-range` [`String`]: a `String` representing the `io.cryostat.agent.ConfigModule.UriRange` enum level that restricts the acceptable hosts specified in the `cryostat.agent.baseuri` property. This is used to control the server locations that this Cryostat Agent instance is willing to register itself with. Default `dns_local`, which means any IP or hostname that is or resolves to `localhost`, a link-local IP address, an IP address from a private range block, or a hostname ending in `.local` will be accepted. If a `cryostat.agent.baseuri` is specified with a host outside of this range then the Agent will refuse to start. Acceptable values are: `loopback`, `link_local`, `site_local`, `dns_local`, and `public`. Each higher/more relaxed level implies that each lower level is also acceptable.
- [x] `cryostat.agent.callback` [`java.net.URI`]: a URL pointing back to this agent, ex. `"https://12.34.56.78:1234/"`. Cryostat will use this URL to perform health checks and request updates from the agent. This reflects the externally-visible IP address/hostname and port where this application and agent can be found.
- [ ] `cryostat.agent.api.writes-enabled` [`boolean`]: Control whether the agent accepts "write" or mutating operations on its HTTP API. Requests for remote operations such as dynamically starting Flight Recordings will be rejected unless this is set. Default `false`.
- [ ] `cryostat.agent.instance-id` [`String`]: a unique ID for this agent instance. This will be used to uniquely identify the agent in the Cryostat discovery database, as well as to unambiguously match its encrypted stored credentials. The default is a random UUID string. It is not recommended to override this value.
Expand Down
19 changes: 19 additions & 0 deletions src/main/java/io/cryostat/agent/Agent.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package io.cryostat.agent;

import java.net.URI;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand All @@ -28,6 +29,7 @@
import javax.inject.Named;
import javax.inject.Singleton;

import io.cryostat.agent.ConfigModule.URIRange;
import io.cryostat.agent.harvest.Harvester;
import io.cryostat.agent.triggers.TriggerEvaluator;

Expand All @@ -46,6 +48,17 @@ public static void main(String[] args) {
AgentExitHandler agentExitHandler = null;
try {
final Client client = DaggerAgent_Client.builder().build();

URI baseUri = client.baseUri();
URIRange uriRange = client.uriRange();
if (!uriRange.validate(baseUri)) {
throw new IllegalArgumentException(
String.format(
"cryostat.agent.baseuri of \"%s\" is unacceptable with URI range"
+ " \"%s\"",
baseUri, uriRange));
}

Registration registration = client.registration();
Harvester harvester = client.harvester();
WebServer webServer = client.webServer();
Expand Down Expand Up @@ -141,6 +154,12 @@ public static void premain(String args) {
@Singleton
@Component(modules = {MainModule.class})
interface Client {
@Named(ConfigModule.CRYOSTAT_AGENT_BASEURI)
URI baseUri();

@Named(ConfigModule.CRYOSTAT_AGENT_BASEURI_RANGE)
URIRange uriRange();

WebServer webServer();

Registration registration();
Expand Down
74 changes: 74 additions & 0 deletions src/main/java/io/cryostat/agent/ConfigModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,14 @@
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import javax.inject.Named;
import javax.inject.Singleton;

import io.cryostat.agent.util.StringUtils;

import dagger.Module;
import dagger.Provides;
import org.eclipse.microprofile.config.Config;
Expand All @@ -38,6 +42,7 @@ public abstract class ConfigModule {
private static final Logger log = LoggerFactory.getLogger(ConfigModule.class);

public static final String CRYOSTAT_AGENT_INSTANCE_ID = "cryostat.agent.instance-id";
public static final String CRYOSTAT_AGENT_BASEURI_RANGE = "cryostat.agent.baseuri-range";
public static final String CRYOSTAT_AGENT_BASEURI = "cryostat.agent.baseuri";
public static final String CRYOSTAT_AGENT_CALLBACK = "cryostat.agent.callback";
public static final String CRYOSTAT_AGENT_REALM = "cryostat.agent.realm";
Expand Down Expand Up @@ -97,6 +102,12 @@ public static Config provideConfig() {
return ConfigProvider.getConfig();
}

@Provides
@Named(CRYOSTAT_AGENT_BASEURI_RANGE)
public static URIRange provideUriRange(Config config) {
return URIRange.fromString(config.getValue(CRYOSTAT_AGENT_BASEURI_RANGE, String.class));
}

@Provides
@Singleton
@Named(CRYOSTAT_AGENT_BASEURI)
Expand Down Expand Up @@ -307,4 +318,67 @@ public static List<String> provideCryostatSmartTriggerDefinitions(Config config)
public static long provideCryostatSmartTriggerEvaluationPeriodMs(Config config) {
return config.getValue(CRYOSTAT_AGENT_SMART_TRIGGER_EVALUATION_PERIOD_MS, long.class);
}

public enum URIRange {
LOOPBACK(u -> check(u, u2 -> true, InetAddress::isLoopbackAddress)),
LINK_LOCAL(
u ->
check(
u,
u2 -> StringUtils.isNotBlank(u2.getHost()),
InetAddress::isLinkLocalAddress)),
SITE_LOCAL(
u ->
check(
u,
u2 -> StringUtils.isNotBlank(u2.getHost()),
InetAddress::isSiteLocalAddress)),
DNS_LOCAL(
u ->
StringUtils.isNotBlank(u.getHost())
&& (u.getHost().endsWith(".local")
|| u.getHost().endsWith(".localhost"))),
PUBLIC(u -> true),
;

private URIRange(Predicate<URI> fn) {
this.fn = fn;
}

private final Predicate<URI> fn;

private static boolean check(URI uri, Predicate<URI> f1, Predicate<InetAddress> f2) {
try {
return f1.test(uri) && f2.test(InetAddress.getByName(uri.getHost()));
} catch (UnknownHostException uhe) {
log.error("Failed to resolve host", uhe);
return false;
}
}

private boolean test(URI uri) {
return fn.test(uri);
}

public boolean validate(URI uri) {
List<URIRange> ranges =
List.of(URIRange.values()).stream()
.filter(r -> r.ordinal() <= this.ordinal())
.collect(Collectors.toList());
boolean match = false;
for (URIRange range : ranges) {
match |= range.test(uri);
}
return match;
}

public static URIRange fromString(String s) {
for (URIRange r : URIRange.values()) {
if (r.name().equalsIgnoreCase(s)) {
return r;
}
}
return SITE_LOCAL;
}
}
}
4 changes: 4 additions & 0 deletions src/main/java/io/cryostat/agent/util/StringUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,8 @@ private StringUtils() {}
public static boolean isBlank(String s) {
return s == null || s.isBlank();
}

public static boolean isNotBlank(String s) {
return !isBlank(s);
}
}
1 change: 1 addition & 0 deletions src/main/resources/META-INF/microprofile-config.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
cryostat.agent.app.name=cryostat-agent
cryostat.agent.baseuri-range=dns_local
cryostat.agent.baseuri=

cryostat.agent.api.writes-enabled=false
Expand Down
172 changes: 172 additions & 0 deletions src/test/java/io/cryostat/agent/URIRangeTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/*
* Copyright The Cryostat Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cryostat.agent;

import java.net.URI;
import java.net.URISyntaxException;

import io.cryostat.agent.ConfigModule.URIRange;

import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

// FIXME we actually perform a DNS lookup to validate the URI. Unit tests should not do actual DNS
// resolution but instead be able to validate what the host symbolically represents.
public class URIRangeTest {

@ParameterizedTest
@CsvSource({
"127.0.0.1, true",
"localhost, true",
"svc.localhost, true",
"169.254.0.0, false",
"169.254.10.5, false",
"168.254.1.1, false",
"10.0.0.10, false",
"10.0.10.10, false",
"10.10.10.10, false",
"11.10.10.10, false",
"172.16.0.0, false",
"172.16.10.5, false",
"172.17.10.5, false",
"172.100.10.5, false",
"192.168.1.1, false",
"192.168.2.1, false",
"192.169.2.1, false",
"svc.local, false",
"example.svc.cluster.local, false",
"example.com, false"
})
public void testLoopbackRange(String s, String v) throws URISyntaxException {
test(URIRange.LOOPBACK, s, v);
}

@ParameterizedTest
@CsvSource({
"127.0.0.1, true",
"localhost, true",
"svc.localhost, true",
"169.254.0.0, true",
"169.254.10.5, true",
"168.254.1.1, false",
"10.0.0.10, false",
"10.0.10.10, false",
"10.10.10.10, false",
"11.10.10.10, false",
"172.16.0.0, false",
"172.16.10.5, false",
"172.17.10.5, false",
"172.100.10.5, false",
"192.168.1.1, false",
"192.168.2.1, false",
"192.169.2.1, false",
"svc.local, false",
"example.svc.cluster.local, false",
"example.com, false"
})
public void testLinkLocalRange(String s, String v) throws URISyntaxException {
test(URIRange.LINK_LOCAL, s, v);
}

@ParameterizedTest
@CsvSource({
"127.0.0.1, true",
"localhost, true",
"svc.localhost, true",
"169.254.0.0, true",
"169.254.10.5, true",
"168.254.1.1, false",
"10.0.0.10, true",
"10.0.10.10, true",
"10.10.10.10, true",
"11.10.10.10, false",
"172.16.0.0, true",
"172.16.10.5, true",
"172.17.10.5, true",
"172.100.10.5, false",
"192.168.1.1, true",
"192.168.2.1, true",
"192.170.2.1, false",
"svc.local, false",
"example.svc.cluster.local, false",
"example.com, false"
})
public void testSiteLocalRange(String s, String v) throws URISyntaxException {
test(URIRange.SITE_LOCAL, s, v);
}

@ParameterizedTest
@CsvSource({
"127.0.0.1, true",
"localhost, true",
"svc.localhost, true",
"169.254.0.0, true",
"169.254.10.5, true",
"168.254.1.1, false",
"10.0.0.10, true",
"10.0.10.10, true",
"10.10.10.10, true",
"11.10.10.10, false",
"172.16.0.0, true",
"172.16.10.5, true",
"172.17.10.5, true",
"172.100.10.5, false",
"192.168.1.1, true",
"192.168.2.1, true",
"192.169.2.1, false",
"svc.local, true",
"example.svc.cluster.local, true",
"example.com, false"
})
public void testDnsLocalRange(String s, String v) throws URISyntaxException {
test(URIRange.DNS_LOCAL, s, v);
}

@ParameterizedTest
@CsvSource({
"127.0.0.1, true",
"localhost, true",
"svc.localhost, true",
"169.254.0.0, true",
"169.254.10.5, true",
"168.254.1.1, true",
"10.0.0.10, true",
"10.0.10.10, true",
"10.10.10.10, true",
"11.10.10.10, true",
"172.16.0.0, true",
"172.16.10.5, true",
"172.17.10.5, true",
"172.100.10.5, true",
"192.168.1.1, true",
"192.168.2.1, true",
"192.169.2.1, true",
"svc.local, true",
"example.svc.cluster.local, true",
"example.com, true"
})
public void testPublicRange(String s, String v) throws URISyntaxException {
test(URIRange.PUBLIC, s, v);
}

private void test(URIRange range, String s, String v) throws URISyntaxException {
boolean expected = Boolean.parseBoolean(v);
URI u = new URI(String.format("http://%s:1234", s));
MatcherAssert.assertThat(s, range.validate(u), Matchers.is(expected));
}
}
Loading