Skip to content

Commit bacba0f

Browse files
committed
Log authentication attempts
Mainly so it's possible to block IPs outright for abuse reasons
1 parent 1b9bd35 commit bacba0f

File tree

3 files changed

+94
-35
lines changed

3 files changed

+94
-35
lines changed

src/main/java/gay/ampflower/maven/Maven.java

Lines changed: 60 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -108,75 +108,107 @@ public static void main(String[] args) throws Exception {
108108
@Override
109109
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
110110
throws IOException {
111+
var host = request.getHeader("Host");
112+
var maven = config.location(host);
113+
var user = Passwd.user(request.getHeader("Authorization"));
114+
115+
logger.info("request: {} {}@{}{} from {}:{} ({}), real: {}, cf: {} / {}, agent: {}", request.getMethod(), user,
116+
host, target, request.getRemoteAddr(), request.getRemotePort(), request.getRemoteHost(),
117+
Utils.toString(request.getHeaders("X-Fowarded-For")), request.getHeader("CF-Connecting-IP"),
118+
request.getHeader("True-Client-IP"), request.getHeader("User-Agent"));
119+
120+
// CF-Connecting-IP and X-Forwarded-For :blobfox_3c:
121+
if (!checkPreconditions(user, baseRequest, request, response)) {
122+
logger.info("invalid: {} {}@{}{} from {}:{} ({})", request.getMethod(), user, host, target,
123+
request.getRemoteAddr(), request.getRemotePort(), request.getRemoteHost());
124+
user.close();
125+
return;
126+
}
127+
128+
logger.info("authenticated: {} {}@{}{} from {}:{} ({})", request.getMethod(), user, host, target,
129+
request.getRemoteAddr(), request.getRemotePort(), request.getRemoteHost());
130+
131+
var path = maven.resolve('.' + target);
132+
if (Files.exists(path) && !target.contains("SNAPSHOT")
133+
&& !target.regionMatches(target.lastIndexOf('/') + 1, "maven-metadata", 0, 14)) {
134+
response.setStatus(HttpServletResponse.SC_CONFLICT);
135+
response.getWriter().println("File cannot be replaced or deleted once uploaded.");
136+
137+
logger.info("attempted replacing: {} {}@{}{}", request.getMethod(), user, host, target);
138+
return;
139+
}
140+
var parent = path.getParent();
141+
if (Files.exists(parent) && !Files.isDirectory(parent)) {
142+
response.setStatus(HttpServletResponse.SC_CONFLICT);
143+
response.getWriter().println("Package is a file.");
144+
145+
logger.info("package is a file: {} {}@{}{}", request.getMethod(), user, host, target);
146+
return;
147+
}
148+
149+
Files.createDirectories(parent);
150+
try (var srvIn = request.getInputStream()) {
151+
Files.copy(srvIn, path, StandardCopyOption.REPLACE_EXISTING);
152+
}
153+
154+
logger.info("successful upload: {} {}@{}{}", request.getMethod(), user, host, target);
155+
response.setStatus(HttpServletResponse.SC_CREATED);
156+
}
157+
158+
private boolean checkPreconditions(Passwd.User user, Request baseRequest, HttpServletRequest request,
159+
HttpServletResponse response) throws IOException {
111160
boolean taint = "http".equals(request.getHeader("X-Forwarded-Proto"));
112161
// No point in executing on any other.
113162
baseRequest.setHandled(true);
114163
if (!"PUT".equalsIgnoreCase(request.getMethod())) {
115164
response.setStatus(HttpServletResponse.SC_NOT_IMPLEMENTED);
116-
return;
165+
return false;
117166
}
118167
int contentLength = request.getContentLength();
119168
// Ignore any upload that is too small or is too large (24M)
120169
if (contentLength < 0) {
121170
response.setStatus(HttpServletResponse.SC_LENGTH_REQUIRED);
122-
return;
171+
return false;
123172
}
124173
if (contentLength > 1024 * 1024 * 24) {
125174
response.setStatus(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE);
126-
return;
175+
return false;
127176
}
128177

129178
var host = request.getHeader("Host");
130179
var maven = config.location(host);
131180
// Check to see if the host is valid.
132181
if (maven == null) {
133182
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
134-
return;
183+
return false;
135184
}
136185
// Check to see if the IP was banned from uploading.
137186
// if(checkObject(baseRequest.getRemoteInetSocketAddress().getAddress())) {
138187
// response.setStatus(HttpServletResponse.SC_FORBIDDEN);
139188
// return;
140189
// }
141-
var authorization = request.getHeader("Authorization");
142-
if (authorization == null || !authorization.startsWith("Basic ") || checkObject(authorization)) {
190+
if (checkObject(request.getHeader("Authorization"))) {
143191
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
144192
response.getWriter().println("Unacceptable Authorization Method");
145-
return;
193+
return false;
146194
}
147195

148196
try {
149-
if (!Passwd.authorized(config, host, authorization, nonce, taint)) {
197+
if (!Passwd.authorized(config, host, user, nonce, taint)) {
150198
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
151199
response.getWriter().println("Invalid credentials.");
152-
return;
200+
return false;
153201
}
154202
} catch (InterruptedException e) {
155203
throw new RuntimeException(e);
156204
}
157205
if (taint) {
158206
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
159207
response.getWriter().println("Use HTTPS next time. Password invalidated, contact sysadmin.");
160-
return;
208+
return false;
161209
}
162-
var path = maven.resolve('.' + target);
163-
if (Files.exists(path) && !target.contains("SNAPSHOT")
164-
&& !target.regionMatches(target.lastIndexOf('/') + 1, "maven-metadata", 0, 14)) {
165-
response.setStatus(HttpServletResponse.SC_CONFLICT);
166-
response.getWriter().println("File cannot be replaced or deleted once uploaded.");
167-
return;
168-
}
169-
var parent = path.getParent();
170-
if (Files.exists(parent) && !Files.isDirectory(parent)) {
171-
response.setStatus(HttpServletResponse.SC_CONFLICT);
172-
response.getWriter().println("Package is a file.");
173-
return;
174-
}
175-
Files.createDirectories(parent);
176-
try (var srvIn = request.getInputStream()) {
177-
Files.copy(srvIn, path, StandardCopyOption.REPLACE_EXISTING);
178-
}
179-
response.setStatus(HttpServletResponse.SC_CREATED);
210+
211+
return true;
180212
}
181213

182214
/**

src/main/java/gay/ampflower/maven/Passwd.java

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,11 @@ public final class Passwd {
3333
Utils.scheduler.scheduleWithFixedDelay(map::clear, 30, 30, TimeUnit.SECONDS);
3434
}
3535

36-
public static boolean authorized(Config config, String host, String authorization, byte[] nonce, boolean taint)
37-
throws InterruptedException {
36+
public static User user(String authorization) {
37+
if (authorization == null || !authorization.startsWith("Basic ")) {
38+
return null;
39+
}
40+
3841
// A MIME decoder can decode regular and URL base64.
3942
var rawAuthorization = Utils.DECODER.decode(authorization.substring(6));
4043
int i = 0;
@@ -44,7 +47,14 @@ public static boolean authorized(Config config, String host, String authorizatio
4447
}
4548
var username = new String(rawAuthorization, 0, i);
4649
byte[] password = Arrays.copyOfRange(rawAuthorization, i + 1, rawAuthorization.length);
50+
Arrays.clear(rawAuthorization);
51+
return new User(username, password);
52+
}
4753

54+
public static boolean authorized(Config config, String host, User user, byte[] nonce, boolean taint)
55+
throws InterruptedException {
56+
var username = user.username();
57+
var password = user.password();
4858
boolean flag;
4959

5060
var hash = config.authHashKey(host, username, password, nonce);
@@ -56,7 +66,6 @@ public static boolean authorized(Config config, String host, String authorizatio
5666
flag = either.b.complete(config.authorized(host, username, password), taint);
5767
}
5868

59-
Arrays.clear(rawAuthorization);
6069
Arrays.clear(password);
6170

6271
if (flag && taint) {
@@ -78,6 +87,18 @@ public static boolean verify(String input, byte[] password, byte[] secret) {
7887
}
7988
}
8089

90+
public record User(String username, byte[] password) implements AutoCloseable {
91+
// #close() is only used to have the IDE nag if you don't "close" the record.
92+
public void close() {
93+
Arrays.clear(password);
94+
}
95+
96+
@Override
97+
public String toString() {
98+
return username + ":???";
99+
}
100+
}
101+
81102
private static Either<Carrier, Carrier> tryLease(Sha256Hash key) {
82103
final var current = map.get(key);
83104
if (current != null) {

src/main/java/gay/ampflower/maven/Utils.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,7 @@
2727
import java.nio.file.attribute.PosixFilePermissions;
2828
import java.security.NoSuchAlgorithmException;
2929
import java.security.SecureRandom;
30-
import java.util.Base64;
31-
import java.util.HashMap;
32-
import java.util.Objects;
33-
import java.util.Set;
30+
import java.util.*;
3431
import java.util.concurrent.Executors;
3532
import java.util.concurrent.ScheduledExecutorService;
3633
import java.util.concurrent.ThreadFactory;
@@ -101,6 +98,15 @@ public static boolean contains(final Object[] array, Object obj) {
10198
return false;
10299
}
103100

101+
public static String toString(Enumeration<?> enumeration) {
102+
var builder = new StringBuilder();
103+
while (enumeration.hasMoreElements()) {
104+
builder.append(enumeration.nextElement()).append(", ");
105+
}
106+
107+
return builder.toString();
108+
}
109+
104110
/**
105111
* Determines secure permissions for a new secret file.
106112
*

0 commit comments

Comments
 (0)