Skip to content
Open
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,6 @@ out/

### VS Code ###
.vscode/

### Logs ###
logs/
162 changes: 162 additions & 0 deletions CONFIGURATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# Configuration Guide

The Jetty-based GitProxy server supports configuration via YAML files and environment variables. This allows you to configure providers, filters, and other server settings without modifying code.

## Configuration Files

The server loads configuration from the following files in order:
1. `src/main/resources/git-proxy.yml` - Base configuration
2. `src/main/resources/git-proxy-local.yml` - Local overrides (merged with base)
3. Environment variables with `GITPROXY_` prefix

Configuration from `git-proxy-local.yml` will override or extend settings from `git-proxy.yml`, and environment variables will override both.

## Environment Variable Overrides

You can override certain configuration values using environment variables with the `GITPROXY_` prefix:

- `GITPROXY_SERVER_PORT`: Override the server port (e.g., `GITPROXY_SERVER_PORT=9090`)
- `GITPROXY_GITPROXY_BASEPATH`: Override the base path (e.g., `GITPROXY_GITPROXY_BASEPATH=/proxy`)

Note: Whitelist configurations are not supported via environment variables due to their complex structure.

## Server Configuration

```yaml
server:
port: 8080 # HTTP port for the server
```

## Provider Configuration

Providers define the Git hosting services that the proxy will forward requests to.

### Built-in Providers

The following providers are built-in:
- `github` - GitHub (https://github.com)
- `gitlab` - GitLab (https://gitlab.com)
- `bitbucket` - Bitbucket (https://bitbucket.org)

### Example Provider Configuration

```yaml
git-proxy:
base-path: "/git" # Optional base path for all servlets
providers:
github:
enabled: true
gitlab:
enabled: true
servlet-path: /custom-gitlab # Optional custom path
bitbucket:
enabled: false
# Custom provider example
internal-github:
enabled: true
servlet-path: /enterprise-github
uri: https://githubserver.example.com
```

### Provider Options

- `enabled` (boolean): Enable/disable the provider
- `uri` (string): Custom URI for the provider (for self-hosted instances)
- `servlet-path` (string): Custom servlet path (default is based on provider name)
- `log-proxy` (boolean): Enable proxy logging (default: true)
- `connect-timeout` (int): Connection timeout in milliseconds (default: -1, no timeout)
- `read-timeout` (int): Read timeout in milliseconds (default: -1, no timeout)

## Filter Configuration

Filters control access to repositories and enforce policies.

Note: GitHub already enforces authentication for push operations using personal access tokens (PATs). The proxy transparently forwards requests upstream and returns errors from GitHub directly, so authentication checking is handled by GitHub itself.

### Whitelist Filters

Control which repositories can be accessed.

```yaml
git-proxy:
filters:
whitelists:
- enabled: true
order: 5
operations:
- FETCH
- PUSH
providers:
- github
slugs:
- coopernetes/test-repo
- finos/git-proxy
- enabled: true
order: 10
operations:
- PUSH
providers:
- gitlab
owners:
- finosfoundation
- enabled: true
order: 20
operations:
- FETCH
providers:
- github
names:
- hello-world
```

Options:
- `enabled` (boolean): Enable/disable the filter
- `order` (int): Filter execution order
- `operations` (list): Git operations to apply filter to (PUSH, FETCH)
- `providers` (list): Provider names to apply filter to
- `slugs` (list): Repository slugs (owner/repo) to whitelist
- `owners` (list): Repository owners to whitelist
- `names` (list): Repository names to whitelist

Note: You can use `slugs`, `owners`, and `names` together in a single whitelist entry, or separately for more granular control.

## Running the Server

```bash
# Build the application
./gradlew build

# Run the application
./gradlew run

# Or run the JAR directly
java -jar build/libs/jgit-proxy-*.jar
```

The server will read configuration from the YAML files and start with the configured providers and filters.

## Example Usage

Once the server is running, you can use it as a proxy for Git operations:

```bash
# Clone via proxy
git clone http://localhost:8080/github.com/finos/git-proxy.git

# Clone from GitLab via proxy
git clone http://localhost:8080/gitlab.com/coopernetes/test-repo.git

# Clone from custom provider
git clone http://localhost:8080/debian/salsa/test-repo.git
```

## Logging

The application uses SLF4J with Logback. You can configure logging levels in the YAML files:

```yaml
logging:
level:
org.finos.gitproxy: DEBUG
org.finos.gitproxy.git: DEBUG
```
40 changes: 39 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,45 @@ This is a simple implementation of a git proxy in Java. This is a possible succe
## Usage
To use this project, you need to have Java 17 or higher installed on your machine. You can run the project using the following command:
```shell
./gradlew bootRun
./gradlew run
```

## Configuration

The Jetty-based server supports YAML-based configuration for providers and filters. Configuration files:
- `src/main/resources/application.yml` - Base configuration
- `src/main/resources/application-local.yml` - Local overrides

See [CONFIGURATION.md](CONFIGURATION.md) for detailed configuration options.

### Quick Configuration Example

Configure providers and filters in `application-local.yml`:

```yaml
server:
port: 8080

git-proxy:
providers:
github:
enabled: true
gitlab:
enabled: true
bitbucket:
enabled: true
filters:
whitelists:
- enabled: true
order: 5
operations:
- FETCH
- PUSH
providers:
- github
slugs:
- coopernetes/test-repo
- finos/git-proxy
```

## Endpoints
Expand Down
6 changes: 3 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ repositories {
}
}

//application {
// mainClass = 'org.finos.gitproxy.GitProxyApplication'
//}
application {
mainClass = 'org.finos.gitproxy.GitProxyApplication'
}

ext {
jgitVersion = '7.0.0.202409031743-r'
Expand Down
104 changes: 56 additions & 48 deletions src/main/java/org/finos/gitproxy/GitProxyApplication.java
Original file line number Diff line number Diff line change
@@ -1,83 +1,91 @@
package org.finos.gitproxy;

import jakarta.servlet.DispatcherType;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.jetty.ee10.servlet.FilterHolder;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.ee10.servlet.ServletHolder;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.finos.gitproxy.git.HttpAuthScheme;
import org.finos.gitproxy.provider.GitHubProvider;
import org.finos.gitproxy.config.JettyConfigurationBuilder;
import org.finos.gitproxy.config.JettyConfigurationLoader;
import org.finos.gitproxy.provider.GitProxyProvider;
import org.finos.gitproxy.servlet.GitProxyServlet;
import org.finos.gitproxy.servlet.filter.*;
import org.finos.gitproxy.servlet.filter.GitProxyFilter;

/**
* Main application class for the Jetty-based GitProxy server. This application reads configuration from YAML files and
* bootstraps the Jetty server with appropriate providers and filters.
*/
@Slf4j
public class GitProxyApplication {
public static void main(String[] args) throws Exception {
// Load configuration
JettyConfigurationLoader configLoader = new JettyConfigurationLoader();
JettyConfigurationBuilder configBuilder = new JettyConfigurationBuilder(configLoader);

// Build providers from configuration
List<GitProxyProvider> providers = configBuilder.buildProviders();

if (providers.isEmpty()) {
log.warn("No providers configured, server will not handle any requests");
}

// Setup Jetty server
var threadPool = new QueuedThreadPool();
threadPool.setName("server");

var server = new Server(threadPool);

// Configure server port
int port = configLoader.getServerPort();
var connector = new ServerConnector(server);
connector.setPort(8080);
connector.setPort(port);
server.addConnector(connector);

var gitHubProvider = new GitHubProvider("");
String urlPattern = gitHubProvider.servletMapping();

var context = new ServletContextHandler("/", false, false);

var forceGitClientFilter = new ForceGitClientFilter();
var forceGitClientFilterHolder = new FilterHolder(forceGitClientFilter);
forceGitClientFilterHolder.setAsyncSupported(true);
context.addFilter(forceGitClientFilterHolder, urlPattern, EnumSet.of(DispatcherType.REQUEST));
// Setup each provider with its filters and servlet
for (GitProxyProvider provider : providers) {
log.info("Configuring provider: {} at {}", provider.getName(), provider.servletMapping());

var parseRequestFilter = new ParseGitRequestFilter(gitHubProvider);
var parseRequestFilterHolder = new FilterHolder(parseRequestFilter);
parseRequestFilterHolder.setAsyncSupported(true);
context.addFilter(parseRequestFilterHolder, urlPattern, EnumSet.of(DispatcherType.REQUEST));
String urlPattern = provider.servletMapping();

var githubAuthorizedFilter = new GitHubUserAuthenticatedFilter(
10, gitHubProvider, Set.of(HttpAuthScheme.BASIC, HttpAuthScheme.TOKEN, HttpAuthScheme.BEARER));
var ghFilterHolder = new FilterHolder(githubAuthorizedFilter);
context.addFilter(ghFilterHolder, urlPattern, EnumSet.of(DispatcherType.REQUEST));
// Build and add filters for this provider
List<GitProxyFilter> filters = configBuilder.buildFiltersForProvider(provider);

var whitelistFilters = List.of(
// new WhitelistByUrlFilter(100, gitHubProvider, List.of("coopernetes"),
// RepositoryUrlFilter.Target.OWNER),
// new WhitelistByUrlFilter(
// 101, gitHubProvider, List.of("jgit-proxy", "test-repo"),
// RepositoryUrlFilter.Target.NAME),
new WhitelistByUrlFilter(
102,
gitHubProvider,
List.of("finos/git-proxy", "coopernetes/test-repo"),
RepositoryUrlFilter.Target.SLUG));
var whitelistAggregateFilter = new WhitelistAggregateFilter(20, gitHubProvider, whitelistFilters);
var whitelistAggFilterHolder = new FilterHolder(whitelistAggregateFilter);
whitelistAggFilterHolder.setAsyncSupported(true);
context.addFilter(whitelistAggFilterHolder, urlPattern, EnumSet.of(DispatcherType.REQUEST));
// Sort filters by order
filters.sort(Comparator.comparingInt(GitProxyFilter::getOrder));

var auditFilter = new AuditLogFilter();
var auditFilterHolder = new FilterHolder(auditFilter);
auditFilterHolder.setAsyncSupported(true);
context.addFilter(auditFilterHolder, urlPattern, EnumSet.of(DispatcherType.REQUEST));
for (GitProxyFilter filter : filters) {
var filterHolder = new FilterHolder(filter);
filterHolder.setAsyncSupported(true);
context.addFilter(filterHolder, urlPattern, EnumSet.of(DispatcherType.REQUEST));
log.debug(
"Added filter {} (order={}) for {}",
filter.getClass().getSimpleName(),
filter.getOrder(),
provider.getName());
}

var proxyServlet = new GitProxyServlet();
var proxyServletHolder = new ServletHolder(proxyServlet);
proxyServletHolder.setInitParameter("proxyTo", "https://github.com");
proxyServletHolder.setInitParameter("prefix", "/github.com");
proxyServletHolder.setInitParameter("hostHeader", "github.com");
proxyServletHolder.setInitParameter("preserveHost", "false");
context.addServlet(proxyServletHolder, urlPattern);
// Add proxy servlet for this provider
var proxyServlet = new GitProxyServlet();
var proxyServletHolder = new ServletHolder(proxyServlet);
proxyServletHolder.setInitParameter("proxyTo", provider.getUri().toString());
proxyServletHolder.setInitParameter("prefix", provider.servletPath());
proxyServletHolder.setInitParameter("hostHeader", provider.getUri().getHost());
proxyServletHolder.setInitParameter("preserveHost", "false");
context.addServlet(proxyServletHolder, urlPattern);
log.info("Added servlet for {} proxying to {}", provider.getName(), provider.getUri());
}

server.setHandler(context);

server.start();
System.out.println("Server started at http://localhost:8080/");
log.info("Server started at http://localhost:{}/", port);
System.out.println("Server started at http://localhost:" + port + "/");
}
}
Loading
Loading