From f57bc5343724fa83932ac6f8ee81f1becf1f86d0 Mon Sep 17 00:00:00 2001 From: yoni Date: Tue, 15 Jul 2025 18:15:15 +0300 Subject: [PATCH 1/6] Adding easy option to use basic credentials for schema-registry (cherry picked from commit 31c05b8569f6e21f530329e92bd873b6870041a8) --- ...actSchemaRegistryOptionsConfigAdapter.java | 21 ++++++++- .../schema/schema.registry.schema.patch.json | 45 ++++++++++++++----- 2 files changed, 53 insertions(+), 13 deletions(-) diff --git a/runtime/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/AbstractSchemaRegistryOptionsConfigAdapter.java b/runtime/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/AbstractSchemaRegistryOptionsConfigAdapter.java index 6c80d96d0b..30ff440b01 100644 --- a/runtime/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/AbstractSchemaRegistryOptionsConfigAdapter.java +++ b/runtime/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/AbstractSchemaRegistryOptionsConfigAdapter.java @@ -17,6 +17,7 @@ import static java.util.stream.Collectors.toList; import java.time.Duration; +import java.util.Base64; import java.util.List; import java.util.Set; import java.util.function.Supplier; @@ -47,6 +48,9 @@ public abstract class AbstractSchemaRegistryOptionsConfigAdapter Date: Sun, 27 Jul 2025 22:34:05 +0300 Subject: [PATCH 2/6] Added demo for Schema-registry basic auth --- examples/http.kafka.avro.json/README.md | 11 +++++++++-- examples/http.kafka.avro.json/compose.yaml | 17 +++++++++++------ examples/http.kafka.avro.json/etc/zilla.yaml | 4 ++++ 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/examples/http.kafka.avro.json/README.md b/examples/http.kafka.avro.json/README.md index 7ea05ee152..6e01c2f5d8 100644 --- a/examples/http.kafka.avro.json/README.md +++ b/examples/http.kafka.avro.json/README.md @@ -1,6 +1,7 @@ # http.kafka.avro.json This example illustrates how to configure the Karapace Schema Registry in Zilla to validate messages during produce and fetch to a Kafka topic. +It also includes a demonstration of how to use basic authentication for the Schema Registry. ## Requirements @@ -18,6 +19,8 @@ docker compose up -d ```bash curl 'http://localhost:8081/subjects/items-snapshots-value/versions' \ +--basic \ +--user 'user1:password1' \ --header 'Content-Type: application/json' \ --data '{ "schema": @@ -35,11 +38,15 @@ output: ## Validate created Schema ```bash -curl 'http://localhost:8081/schemas/ids/1' +curl 'http://localhost:8081/schemas/ids/1' \ +--basic \ +--user 'user1:password1' ``` ```bash -curl 'http://localhost:8081/subjects/items-snapshots-value/versions/latest' +curl 'http://localhost:8081/subjects/items-snapshots-value/versions/latest' \ +--basic \ +--user 'user1:password1' ``` ## Verify behavior for a valid event diff --git a/examples/http.kafka.avro.json/compose.yaml b/examples/http.kafka.avro.json/compose.yaml index 76c4b466b8..3d1f9efe83 100644 --- a/examples/http.kafka.avro.json/compose.yaml +++ b/examples/http.kafka.avro.json/compose.yaml @@ -5,7 +5,7 @@ services: restart: unless-stopped hostname: zilla.examples.dev ports: - - 7114:7114 + - 7314:7114 healthcheck: interval: 5s timeout: 3s @@ -14,6 +14,8 @@ services: environment: KAFKA_BOOTSTRAP_SERVER: kafka.examples.dev:29092 SCHEMA_REGISTRY_SERVER: http://karapace-registry:8081 + SCHEMA_REGISTRY_USERNAME: user1 + SCHEMA_REGISTRY_PASSWORD: password1 ZILLA_INCUBATOR_ENABLED: "true" volumes: - ./etc:/etc/zilla @@ -24,7 +26,7 @@ services: restart: unless-stopped hostname: kafka.examples.dev ports: - - 9092:9092 + - 9392:9092 healthcheck: test: /opt/bitnami/kafka/bin/kafka-cluster.sh cluster-id --bootstrap-server kafka.examples.dev:29092 || exit 1 interval: 1s @@ -70,7 +72,7 @@ services: image: ghcr.io/kafbat/kafka-ui:v1.0.0 restart: unless-stopped ports: - - 8080:8080 + - 8380:8080 depends_on: kafka: condition: service_healthy @@ -90,19 +92,22 @@ services: build: context: .. dockerfile: container/Dockerfile - entrypoint: + entrypoint: - /bin/bash - - /opt/karapace/start.sh + - /opt/karapace/start.sh - registry + volumes: + - ./users.json:/tmp/users.json:ro depends_on: - kafka ports: - - 8081:8081 + - 8381:8081 environment: KARAPACE_ADVERTISED_HOSTNAME: karapace-registry KARAPACE_BOOTSTRAP_URI: kafka.examples.dev:29092 KARAPACE_PORT: 8081 KARAPACE_HOST: 0.0.0.0 + KARAPACE_REGISTRY_AUTHFILE: /tmp/users.json KARAPACE_CLIENT_ID: karapace KARAPACE_GROUP_ID: karapace-registry KARAPACE_MASTER_ELIGIBILITY: "true" diff --git a/examples/http.kafka.avro.json/etc/zilla.yaml b/examples/http.kafka.avro.json/etc/zilla.yaml index 92e7a3c9fe..06ed2e8f3a 100644 --- a/examples/http.kafka.avro.json/etc/zilla.yaml +++ b/examples/http.kafka.avro.json/etc/zilla.yaml @@ -5,6 +5,10 @@ catalogs: type: schema-registry options: url: ${{env.SCHEMA_REGISTRY_SERVER}} + credentials: + basic: + username: ${{env.SCHEMA_REGISTRY_USERNAME}} + password: ${{env.SCHEMA_REGISTRY_PASSWORD}} context: default bindings: north_tcp_server: From a0c38583ed23d7f4affb89341b2ac26ab03784e3 Mon Sep 17 00:00:00 2001 From: yoni Date: Tue, 29 Jul 2025 13:05:29 +0300 Subject: [PATCH 3/6] Adding missing `user.json` file --- examples/http.kafka.avro.json/users.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 examples/http.kafka.avro.json/users.json diff --git a/examples/http.kafka.avro.json/users.json b/examples/http.kafka.avro.json/users.json new file mode 100644 index 0000000000..48485328b9 --- /dev/null +++ b/examples/http.kafka.avro.json/users.json @@ -0,0 +1,17 @@ +{ + "users": [ + { + "username": "user1", + "algorithm": "sha512", + "salt": "KHOXN_9AmhX17BaUT1CPww", + "password_hash": "N9AReOAyqHYuCXZ4w5hTr7BIj6NguPfl1EoMZaRqOWD\/\/jnBlRL6V3cDnTYF5ZEaVKKZu76PNnYnq8HJBDz9xQ==" + } + ], + "permissions": [ + { + "username": "user1", + "operation": "Write", + "resource": ".*" + } + ] +} \ No newline at end of file From e5616b754842f6c52778247f18360c867e289984 Mon Sep 17 00:00:00 2001 From: yoni Date: Tue, 29 Jul 2025 13:20:11 +0300 Subject: [PATCH 4/6] Adding unit-tests for schema-registry basic credentials --- ...chemaRegistryOptionsConfigAdapterTest.java | 28 +++++++++++++++++++ .../schema/registry/config/catalog.yaml | 4 +++ 2 files changed, 32 insertions(+) diff --git a/runtime/catalog-schema-registry/src/test/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/config/SchemaRegistryOptionsConfigAdapterTest.java b/runtime/catalog-schema-registry/src/test/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/config/SchemaRegistryOptionsConfigAdapterTest.java index f02cc57a55..36a7d13117 100644 --- a/runtime/catalog-schema-registry/src/test/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/config/SchemaRegistryOptionsConfigAdapterTest.java +++ b/runtime/catalog-schema-registry/src/test/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/config/SchemaRegistryOptionsConfigAdapterTest.java @@ -20,6 +20,7 @@ import static org.hamcrest.Matchers.nullValue; import java.time.Duration; +import java.util.Base64; import java.util.List; import jakarta.json.bind.Jsonb; @@ -82,6 +83,33 @@ public void shouldReadOptions() assertThat(catalog.authorization, equalTo("Basic dXNlcjpzZWNyZXQ=")); } + @Test + public void shouldReadOptionsWithBasicCredentials() + { + String text = """ + { + "url": "http://localhost:8081", + "context": "default", + "credentials": + { + "basic": + { + "username": "user", + "password": "secret" + } + } + } + """; + + SchemaRegistryOptionsConfig catalog = jsonb.fromJson(text, SchemaRegistryOptionsConfig.class); + + assertThat(catalog, not(nullValue())); + assertThat(catalog.url, equalTo("http://localhost:8081")); + assertThat(catalog.context, equalTo("default")); + String base64Encoded = Base64.getEncoder().encodeToString("user:secret".getBytes()); + assertThat(catalog.authorization, equalTo("Basic " + base64Encoded)); + } + @Test public void shouldWriteOptions() { diff --git a/specs/catalog-schema-registry.spec/src/main/scripts/io/aklivity/zilla/specs/catalog/schema/registry/config/catalog.yaml b/specs/catalog-schema-registry.spec/src/main/scripts/io/aklivity/zilla/specs/catalog/schema/registry/config/catalog.yaml index a7b776a31c..c076b87763 100644 --- a/specs/catalog-schema-registry.spec/src/main/scripts/io/aklivity/zilla/specs/catalog/schema/registry/config/catalog.yaml +++ b/specs/catalog-schema-registry.spec/src/main/scripts/io/aklivity/zilla/specs/catalog/schema/registry/config/catalog.yaml @@ -22,3 +22,7 @@ catalogs: url: http://localhost:8081 context: default max-age: 30 + credentials: + basic: + username: user1 + password: password1 From a727065abd27654701890a8384fb030155130a14 Mon Sep 17 00:00:00 2001 From: yoni Date: Sun, 10 Aug 2025 17:48:12 +0300 Subject: [PATCH 5/6] Moving basic creds logic to SchemaRegistryCatalogHandler --- .../config/KarapaceOptionsConfig.java | 6 ++++-- .../config/KarapaceOptionsConfigBuilder.java | 14 +++++++++++-- ...actSchemaRegistryOptionsConfigAdapter.java | 18 ++++++++--------- .../AbstractSchemaRegistryOptionsConfig.java | 20 ++++++++++++------- ...actSchemaRegistryOptionsConfigBuilder.java | 20 +++++++++++++++++-- .../config/SchemaRegistryOptionsConfig.java | 6 ++++-- .../SchemaRegistryOptionsConfigBuilder.java | 14 +++++++++++-- .../handler/SchemaRegistryCatalogHandler.java | 12 ++++++++++- 8 files changed, 82 insertions(+), 28 deletions(-) diff --git a/runtime/catalog-karapace/src/main/java/io/aklivity/zilla/runtime/catalog/karapace/config/KarapaceOptionsConfig.java b/runtime/catalog-karapace/src/main/java/io/aklivity/zilla/runtime/catalog/karapace/config/KarapaceOptionsConfig.java index b4a8c38c7c..9688b6ec39 100644 --- a/runtime/catalog-karapace/src/main/java/io/aklivity/zilla/runtime/catalog/karapace/config/KarapaceOptionsConfig.java +++ b/runtime/catalog-karapace/src/main/java/io/aklivity/zilla/runtime/catalog/karapace/config/KarapaceOptionsConfig.java @@ -41,8 +41,10 @@ public static KarapaceOptionsConfigBuilder builder( List keys, List trust, boolean trustcacerts, - String authorization) + String authorization, + String username, + String password) { - super(url, context, maxAge, keys, trust, trustcacerts, authorization); + super(url, context, maxAge, keys, trust, trustcacerts, authorization, username, password); } } diff --git a/runtime/catalog-karapace/src/main/java/io/aklivity/zilla/runtime/catalog/karapace/config/KarapaceOptionsConfigBuilder.java b/runtime/catalog-karapace/src/main/java/io/aklivity/zilla/runtime/catalog/karapace/config/KarapaceOptionsConfigBuilder.java index 66eeb2b573..8713c6ea00 100644 --- a/runtime/catalog-karapace/src/main/java/io/aklivity/zilla/runtime/catalog/karapace/config/KarapaceOptionsConfigBuilder.java +++ b/runtime/catalog-karapace/src/main/java/io/aklivity/zilla/runtime/catalog/karapace/config/KarapaceOptionsConfigBuilder.java @@ -45,8 +45,18 @@ protected KarapaceOptionsConfig newOptionsConfig( List keys, List trust, boolean trustcacerts, - String authorization) + String authorization, + String username, + String password) { - return new KarapaceOptionsConfig(url, context, maxAge, keys, trust, trustcacerts, authorization); + return new KarapaceOptionsConfig(url, + context, + maxAge, + keys, + trust, + trustcacerts, + authorization, + username, + password); } } diff --git a/runtime/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/AbstractSchemaRegistryOptionsConfigAdapter.java b/runtime/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/AbstractSchemaRegistryOptionsConfigAdapter.java index 30ff440b01..8031b4ec8c 100644 --- a/runtime/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/AbstractSchemaRegistryOptionsConfigAdapter.java +++ b/runtime/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/AbstractSchemaRegistryOptionsConfigAdapter.java @@ -17,7 +17,6 @@ import static java.util.stream.Collectors.toList; import java.time.Duration; -import java.util.Base64; import java.util.List; import java.util.Set; import java.util.function.Supplier; @@ -193,21 +192,20 @@ public OptionsConfig adaptFromJson( JsonObject headers = credentials.getJsonObject(AUTHORIZATION_CREDENTIALS_HEADERS_NAME); JsonObject basic = credentials.getJsonObject(AUTHORIZATION_CREDENTIALS_BASIC_NAME); - String authorization; if (headers != null) { - authorization = headers.getString(AUTHORIZATION_NAME); + String authorization = headers.getString(AUTHORIZATION_NAME, null); + options.authorization(authorization); } - else + + if (basic != null) { - String username = basic.getString(AUTHORIZATION_CREDENTIALS_BASIC_USERNAME_NAME); - String password = basic.getString(AUTHORIZATION_CREDENTIALS_BASIC_PASSWORD_NAME); - authorization = "Basic " + Base64.getEncoder().encodeToString( - (username + ":" + password).getBytes()); + String username = basic.getString(AUTHORIZATION_CREDENTIALS_BASIC_USERNAME_NAME, null); + options.username(username); + String password = basic.getString(AUTHORIZATION_CREDENTIALS_BASIC_PASSWORD_NAME, null); + options.password(password); } - - options.authorization(authorization); } } diff --git a/runtime/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/config/AbstractSchemaRegistryOptionsConfig.java b/runtime/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/config/AbstractSchemaRegistryOptionsConfig.java index 0e1f957ea8..c9eca8dc9a 100644 --- a/runtime/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/config/AbstractSchemaRegistryOptionsConfig.java +++ b/runtime/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/config/AbstractSchemaRegistryOptionsConfig.java @@ -28,15 +28,19 @@ public abstract class AbstractSchemaRegistryOptionsConfig extends OptionsConfig public final List trust; public final boolean trustcacerts; public final String authorization; + public final String username; + public final String password; protected AbstractSchemaRegistryOptionsConfig( - String url, - String context, - Duration maxAge, - List keys, - List trust, - boolean trustcacerts, - String authorization) + String url, + String context, + Duration maxAge, + List keys, + List trust, + boolean trustcacerts, + String authorization, + String username, + String password) { this.url = url; this.context = context; @@ -45,5 +49,7 @@ protected AbstractSchemaRegistryOptionsConfig( this.trust = trust; this.trustcacerts = trustcacerts; this.authorization = authorization; + this.username = username; + this.password = password; } } diff --git a/runtime/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/config/AbstractSchemaRegistryOptionsConfigBuilder.java b/runtime/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/config/AbstractSchemaRegistryOptionsConfigBuilder.java index da6833a616..fbb4905dd8 100644 --- a/runtime/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/config/AbstractSchemaRegistryOptionsConfigBuilder.java +++ b/runtime/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/config/AbstractSchemaRegistryOptionsConfigBuilder.java @@ -35,6 +35,8 @@ public abstract class AbstractSchemaRegistryOptionsConfigBuilder trust; private Boolean trustcacerts; private String authorization; + private String username; + private String password; public B url( String url) @@ -85,12 +87,24 @@ public B authorization( return thisType().cast(this); } + public B username(String username) + { + this.username = username; + return thisType().cast(this); + } + + public B password(String password) + { + this.password = password; + return thisType().cast(this); + } + @Override public T build() { Duration maxAge = (this.maxAge != null) ? this.maxAge : MAX_AGE_DEFAULT; final boolean trustcacerts = this.trustcacerts == null ? this.trust == null : this.trustcacerts; - return mapper.apply(newOptionsConfig(url, context, maxAge, keys, trust, trustcacerts, authorization)); + return mapper.apply(newOptionsConfig(url, context, maxAge, keys, trust, trustcacerts, authorization, username, password)); } protected AbstractSchemaRegistryOptionsConfigBuilder( @@ -106,5 +120,7 @@ protected abstract AbstractSchemaRegistryOptionsConfig newOptionsConfig( List keys, List trust, boolean trustcacerts, - String authorization); + String authorization, + String username, + String password); } diff --git a/runtime/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/config/SchemaRegistryOptionsConfig.java b/runtime/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/config/SchemaRegistryOptionsConfig.java index 73f955dcfe..4a507100ef 100644 --- a/runtime/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/config/SchemaRegistryOptionsConfig.java +++ b/runtime/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/config/SchemaRegistryOptionsConfig.java @@ -40,8 +40,10 @@ public static SchemaRegistryOptionsConfigBuilder builder( List keys, List trust, boolean trustcacerts, - String authorization) + String authorization, + String username, + String password) { - super(url, context, maxAge, keys, trust, trustcacerts, authorization); + super(url, context, maxAge, keys, trust, trustcacerts, authorization, username, password); } } diff --git a/runtime/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/config/SchemaRegistryOptionsConfigBuilder.java b/runtime/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/config/SchemaRegistryOptionsConfigBuilder.java index 6d595f0ad0..b2ddbf6680 100644 --- a/runtime/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/config/SchemaRegistryOptionsConfigBuilder.java +++ b/runtime/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/config/SchemaRegistryOptionsConfigBuilder.java @@ -44,9 +44,19 @@ protected SchemaRegistryOptionsConfig newOptionsConfig( List keys, List trust, boolean trustcacerts, - String authorization) + String authorization, + String username, + String password) { - return new SchemaRegistryOptionsConfig(url, context, maxAge, keys, trust, trustcacerts, authorization); + return new SchemaRegistryOptionsConfig(url, + context, + maxAge, + keys, + trust, + trustcacerts, + authorization, + username, + password); } } diff --git a/runtime/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/handler/SchemaRegistryCatalogHandler.java b/runtime/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/handler/SchemaRegistryCatalogHandler.java index ce9d5ddffa..fbc654f54e 100644 --- a/runtime/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/handler/SchemaRegistryCatalogHandler.java +++ b/runtime/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/handler/SchemaRegistryCatalogHandler.java @@ -24,6 +24,7 @@ import java.nio.ByteOrder; import java.security.KeyStore; import java.security.SecureRandom; +import java.util.Base64; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentMap; @@ -143,7 +144,16 @@ public SchemaRegistryCatalogHandler( this.catalogId = catalog.id; this.cachedSchemas = catalog.cache.schemas; this.cachedSchemaIds = catalog.cache.schemaIds; - this.authorization = options.authorization; + if (options.username != null && options.password != null) + { + String base64Creds = + Base64.getEncoder().encodeToString((options.username + ":" + options.password).getBytes()); + this.authorization = "Basic " + base64Creds; + } + else + { + this.authorization = options.authorization; + } } @Override From f3852d04776b94cb6fca4b7e68c858b09837b6e0 Mon Sep 17 00:00:00 2001 From: yoni Date: Mon, 11 Aug 2025 13:29:59 +0300 Subject: [PATCH 6/6] Fix tests --- .../config/SchemaRegistryOptionsConfigAdapterTest.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/runtime/catalog-schema-registry/src/test/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/config/SchemaRegistryOptionsConfigAdapterTest.java b/runtime/catalog-schema-registry/src/test/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/config/SchemaRegistryOptionsConfigAdapterTest.java index 36a7d13117..27b2a3acff 100644 --- a/runtime/catalog-schema-registry/src/test/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/config/SchemaRegistryOptionsConfigAdapterTest.java +++ b/runtime/catalog-schema-registry/src/test/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/config/SchemaRegistryOptionsConfigAdapterTest.java @@ -20,7 +20,6 @@ import static org.hamcrest.Matchers.nullValue; import java.time.Duration; -import java.util.Base64; import java.util.List; import jakarta.json.bind.Jsonb; @@ -106,8 +105,8 @@ public void shouldReadOptionsWithBasicCredentials() assertThat(catalog, not(nullValue())); assertThat(catalog.url, equalTo("http://localhost:8081")); assertThat(catalog.context, equalTo("default")); - String base64Encoded = Base64.getEncoder().encodeToString("user:secret".getBytes()); - assertThat(catalog.authorization, equalTo("Basic " + base64Encoded)); + assertThat(catalog.username, equalTo("user")); + assertThat(catalog.password, equalTo("secret")); } @Test