Skip to content

Commit

Permalink
Full Shiro protection.
Browse files Browse the repository at this point in the history
 - Authentication is now enabled by default. Default is the dummy admin/admin credentials, see shiro.ini
 - shiro.ini now embedded in app. can still be configured/overridden
 - REST endpoints are protected, requiring JWT specified in a request header ('Authorization: Bearer <jwt>')
 - The JWT for a user can be gained at the /jwt URL
 - add 'remember me' to login page

 ref: #604
  • Loading branch information
michaelsembwever committed Jan 21, 2019
1 parent 4124b79 commit 6142e86
Show file tree
Hide file tree
Showing 17 changed files with 250 additions and 113 deletions.
13 changes: 6 additions & 7 deletions src/packaging/resource/cassandra-reaper-cassandra-ssl.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Copyright 2015-2017 Spotify AB
# Copyright 2016-2018 The Last Pickle Ltd
# Copyright 2016-2019 The Last Pickle Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -134,9 +134,8 @@ autoScheduling:
# - type: log
# logger: metrics

# Uncomment the following block to enable authentication

#accessControl:
# sessionTimeout: PT10M
# shiro:
# iniConfigs: ["file:/path/to/shiro.ini"]
# Authentication is enabled by default
accessControl:
sessionTimeout: PT10M
shiro:
iniConfigs: ["classpath:shiro.ini"]
13 changes: 6 additions & 7 deletions src/packaging/resource/cassandra-reaper-cassandra.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Copyright 2015-2017 Spotify AB
# Copyright 2016-2018 The Last Pickle Ltd
# Copyright 2016-2019 The Last Pickle Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -128,9 +128,8 @@ autoScheduling:
# - type: log
# logger: metrics

# Uncomment the following block to enable authentication

#accessControl:
# sessionTimeout: PT10M
# shiro:
# iniConfigs: ["file:/path/to/shiro.ini"]
# Authentication is enabled by default
accessControl:
sessionTimeout: PT10M
shiro:
iniConfigs: ["classpath:shiro.ini"]
13 changes: 6 additions & 7 deletions src/packaging/resource/cassandra-reaper-h2.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Copyright 2015-2017 Spotify AB
# Copyright 2016-2018 The Last Pickle Ltd
# Copyright 2016-2019 The Last Pickle Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -112,9 +112,8 @@ autoScheduling:
# - type: log
# logger: metrics

# Uncomment the following block to enable authentication

#accessControl:
# sessionTimeout: PT10M
# shiro:
# iniConfigs: ["file:/path/to/shiro.ini"]
# Authentication is enabled by default
accessControl:
sessionTimeout: PT10M
shiro:
iniConfigs: ["classpath:shiro.ini"]
13 changes: 6 additions & 7 deletions src/packaging/resource/cassandra-reaper-memory.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Copyright 2015-2017 Spotify AB
# Copyright 2016-2018 The Last Pickle Ltd
# Copyright 2016-2019 The Last Pickle Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -107,9 +107,8 @@ autoScheduling:
# - type: log
# logger: metrics

# Uncomment the following block to enable authentication

#accessControl:
# sessionTimeout: PT10M
# shiro:
# iniConfigs: ["file:/path/to/shiro.ini"]
# Authentication is enabled by default
accessControl:
sessionTimeout: PT10M
shiro:
iniConfigs: ["classpath:shiro.ini"]
13 changes: 6 additions & 7 deletions src/packaging/resource/cassandra-reaper-postgres.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Copyright 2015-2017 Spotify AB
# Copyright 2016-2018 The Last Pickle Ltd
# Copyright 2016-2019 The Last Pickle Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -112,9 +112,8 @@ autoScheduling:
# - type: log
# logger: metrics

# Uncomment the following block to enable authentication

#accessControl:
# sessionTimeout: PT10M
# shiro:
# iniConfigs: ["file:/path/to/shiro.ini"]
# Authentication is enabled by default
accessControl:
sessionTimeout: PT10M
shiro:
iniConfigs: ["classpath:shiro.ini"]
13 changes: 6 additions & 7 deletions src/packaging/resource/cassandra-reaper.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Copyright 2015-2017 Spotify AB
# Copyright 2016-2018 The Last Pickle Ltd
# Copyright 2016-2019 The Last Pickle Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -107,9 +107,8 @@ autoScheduling:
# - type: log
# logger: metrics

# Uncomment the following block to enable authentication

#accessControl:
# sessionTimeout: PT10M
# shiro:
# iniConfigs: ["file:/path/to/shiro.ini"]
# Authentication is enabled by default
accessControl:
sessionTimeout: PT10M
shiro:
iniConfigs: ["classpath:shiro.ini"]
7 changes: 6 additions & 1 deletion src/server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
<packaging>jar</packaging>

<properties>
<dropwizard.version>1.1.8</dropwizard.version>
<dropwizard.version>1.3.8</dropwizard.version>
<docker.directory>src/main/docker</docker.directory>
<timestamp>${maven.build.timestamp}</timestamp>
<maven.build.timestamp.format>yyyy-MM-dd HH:mm:ss</maven.build.timestamp.format>
Expand Down Expand Up @@ -181,6 +181,11 @@
<artifactId>dropwizard-shiro</artifactId>
<version>0.2.0</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<!-- fixes bug around duplicate requests
- https://stackoverflow.com/questions/37956741/jersey-resource-receiving-duplicate-requests-from-jersey-client#39377403
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Copyright 2014-2017 Spotify AB
* Copyright 2016-2018 The Last Pickle Ltd
* Copyright 2016-2019 The Last Pickle Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -30,6 +30,7 @@
import io.cassandrareaper.resources.SnapshotResource;
import io.cassandrareaper.resources.auth.LoginResource;
import io.cassandrareaper.resources.auth.ShiroExceptionMapper;
import io.cassandrareaper.resources.auth.ShiroJwtProvider;
import io.cassandrareaper.service.AutoSchedulingManager;
import io.cassandrareaper.service.PurgeService;
import io.cassandrareaper.service.RepairManager;
Expand Down Expand Up @@ -243,7 +244,8 @@ public void run(ReaperApplicationConfiguration config, Environment environment)
environment.getApplicationContext().setSessionHandler(sessionHandler);
environment.servlets().setSessionHandler(sessionHandler);
environment.jersey().register(new ShiroExceptionMapper());
environment.jersey().register(new LoginResource(context));
environment.jersey().register(new LoginResource());
environment.jersey().register(new ShiroJwtProvider(context));
}

Thread.sleep(1000);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,13 @@

package io.cassandrareaper.resources.auth;

import io.cassandrareaper.AppContext;

import java.io.IOException;
import java.util.Map;

import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import com.google.common.collect.Maps;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
Expand All @@ -40,27 +33,22 @@

@Path("/")
public class LoginResource {
private final AppContext context;

public LoginResource(AppContext context) {
this.context = context;
}

@Path("/login")
@POST
public void login(
@FormParam("username") String username,
@FormParam("password") String password,
@Auth Subject subject)
throws IOException {
@FormParam("rememberMe") boolean rememberMe,
@Auth Subject subject) throws IOException {

ensurePresent(username, "Invalid credentials: missing username.");
ensurePresent(password, "Invalid credentials: missing password.");

try {
subject.login(new UsernamePasswordToken(username, password));
subject.login(new UsernamePasswordToken(username, password, rememberMe));
} catch (AuthenticationException e) {
throw new IncorrectCredentialsException(
"Invalid credentials combination for user: " + username);
throw new IncorrectCredentialsException("Invalid credentials combination for user: " + username);
}
}

Expand All @@ -75,14 +63,4 @@ private void ensurePresent(String value, String message) {
throw new IncorrectCredentialsException(message);
}
}

@Produces(MediaType.APPLICATION_JSON)
@Path("/loginRequired")
@GET
public Response loginRequired() {
Map<String, Boolean> authRequired = Maps.newHashMap();
authRequired.put("auth", context.config.isAccessControlEnabled());

return Response.ok().entity(authRequired).build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
*
* Copyright 2019-2019 The Last Pickle Ltd
*
* 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.cassandrareaper.resources.auth;

import io.cassandrareaper.AppContext;
import io.cassandrareaper.storage.IDistributedStorage;

import java.io.IOException;
import java.security.Key;

import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.xml.bind.DatatypeConverter;

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

@Path("/jwt")
public final class ShiroJwtProvider {

static volatile Key SIGNING_KEY;
private static final SignatureAlgorithm SIG_ALG = SignatureAlgorithm.HS256;

public ShiroJwtProvider(AppContext cxt) {
SIGNING_KEY = getSigningKey(cxt);
}

@GET
public Response get(@Context HttpServletRequest request) throws IOException {
return Response
.ok()
.entity(
Jwts.builder().setSubject(request.getUserPrincipal().getName()).signWith(SIG_ALG, SIGNING_KEY).compact())
.build();
}

private static Key getSigningKey(AppContext cxt) {
String txt = System.getenv("JWT_SECRET");
if (null == txt) {
txt = cxt.storage instanceof IDistributedStorage
? cxt.config.getCassandraFactory().getClusterName()
: AppContext.REAPER_INSTANCE_ADDRESS;
}
return new SecretKeySpec(DatatypeConverter.parseBase64Binary(txt), SIG_ALG.getJcaName());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
*
* Copyright 2019-2019 The Last Pickle Ltd
*
* 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.cassandrareaper.resources.auth;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import io.jsonwebtoken.Jwts;
import org.apache.shiro.web.filter.AccessControlFilter;

public final class ShiroJwtVerifyingFilter extends AccessControlFilter {

public ShiroJwtVerifyingFilter() {}

@Override
protected boolean isAccessAllowed(ServletRequest req, ServletResponse res, Object mappedValue) throws Exception {
if (getSubject(req, res).isRemembered() || getSubject(req, res).isAuthenticated()) {
return true;
}
HttpServletRequest httpRequest = (HttpServletRequest) req;
String jwt = httpRequest.getHeader("Authorization");
if (null == jwt || !jwt.startsWith("Bearer ")) {
return false;
}
jwt = jwt.substring(jwt.indexOf(' ') + 1);
return Jwts.parser().setSigningKey(ShiroJwtProvider.SIGNING_KEY).isSigned(jwt);
}

@Override
protected boolean onAccessDenied(ServletRequest req, ServletResponse res) throws Exception {
HttpServletResponse response = (HttpServletResponse) res;
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,10 @@ public CassandraStorage(ReaperApplicationConfiguration config, Environment envir
overrideQueryOptions(cassandraFactory);
overrideRetryPolicy(cassandraFactory);
overridePoolingOptions(cassandraFactory);

// https://docs.datastax.com/en/developer/java-driver/3.5/manual/metrics/#metrics-4-compatibility
cassandraFactory.setJmxEnabled(false);

cassandra = cassandraFactory.build(environment);
if (config.getActivateQueryLogger()) {
cassandra.register(QueryLogger.builder().build());
Expand Down
Loading

0 comments on commit 6142e86

Please sign in to comment.