Skip to content

Commit

Permalink
[HWORKS-743] Move API auth to separate module and add API key authent…
Browse files Browse the repository at this point in the history
…ication to hopsworks-ca (#1438)

* [HWORKS-743] [HWORKS-743] Move API auth to separate module

[HWORKS-743] Annotate CA endpoints

[HWORKS-743] Dedicated scope for PKI

[HWORKS-743] Move InvalidQueryException to hopsworks-persistence package

[HWORKS-743] Bump module version

[HWORKS-743] Fix license

* [HWORKS-743] Fixes for CE

* [HWORKS-743] Remove empty files
  • Loading branch information
kouzant authored Dec 21, 2023
1 parent 672c238 commit b053bc3
Show file tree
Hide file tree
Showing 132 changed files with 1,368 additions and 1,281 deletions.
2 changes: 1 addition & 1 deletion hopsworks-IT/src/test/ruby/spec/conf_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@

expect(conf_dto['items'].length > 0).to be true

hidden = conf_dto['items'].select {|c| c['name'].eql?("service_master_jwt")}
hidden = conf_dto['items'].select {|c| c['name'].eql?("int_service_api_key")}
expect(hidden[0]['hide']).to be true
end

Expand Down
82 changes: 0 additions & 82 deletions hopsworks-IT/src/test/ruby/spec/jwt_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,6 @@
before :all do
reset_session
end

context "#not logged in" do
it "should not be able to renew service JWT" do
put "#{ENV['HOPSWORKS_API']}/jwt/service", {
token: "some_token",
expiresAt: "1234",
nbf: "1234"
}
expect_status_details(401)
end

it "should not be able to invalidate JWT" do
delete "#{ENV['HOPSWORKS_API']}/jwt/service/some_token"
expect_status_details(401)
end
end

context "#users" do
before :all do
Expand Down Expand Up @@ -82,72 +66,6 @@
refresh_variables
reset_session
end

it "should be able to renew master jwt" do

now = Time.now
not_before = now.strftime("%Y-%m-%dT%H:%M:%S.%L%z")
exp = now + 300
new_expiration = exp.strftime("%Y-%m-%dT%H:%M:%S.%L%z")

# Use one-time token
Airborne.configure do |config|
config.headers = {}
config.headers["Authorization"] = "Bearer #{@renew_tokens[0]}"
end
sleep 1
put "#{ENV['HOPSWORKS_API']}/jwt/service",
{
token: @master_token,
expiresAt: new_expiration,
nbf: not_before
}

expect_status_details(200)

new_master_token = json_body[:jwt][:token]
new_one_time_tokens = json_body[:renewTokens]
expect(new_master_token).not_to be_nil
expect(new_master_token).not_to be_empty

expect(new_one_time_tokens.length).to eql(5)

master_jwt = JWT.decode new_master_token, nil, false

exp_response = Time.at(master_jwt[0]['exp'])
nbf_response = Time.at(master_jwt[0]['nbf'])
# Do not compare milliseconds, there might be different due to conversion
expect(now.strftime("%Y-%m-%dT%H:%M:%S%z")).to eql(nbf_response.strftime("%Y-%m-%dT%H:%M:%S%z"))
expect(exp.strftime("%Y-%m-%dT%H:%M:%S%z")).to eql(exp_response.strftime("%Y-%m-%dT%H:%M:%S%z"))

# Previous token should still be valid
Airborne.configure do |config|
config.headers["Authorization"] = "Bearer #{@master_token}"
end
get "#{ENV['HOPSWORKS_CA']}/token"
expect_status_details(200)

# Invalidate previous master token
Airborne.configure do |config|
config.headers["Authorization"] = "Bearer #{new_master_token}"
end
delete "#{ENV['HOPSWORKS_API']}/jwt/service/#{@master_token}"
expect_status_details(200)

# Subsequent calls with the old master key should fail
Airborne.configure do |config|
config.headers["Authorization"] = "Bearer #{@master_token}"
end
get "#{ENV['HOPSWORKS_CA']}/token"
expect_status_details(401)

# But new master should be still valid...
Airborne.configure do |config|
config.headers["Authorization"] = "Bearer #{new_master_token}"
end
get "#{ENV['HOPSWORKS_CA']}/token"
expect_status_details(200)
end
end

end
Expand Down
78 changes: 78 additions & 0 deletions hopsworks-api-auth/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<!--
~ This file is part of Hopsworks
~ Copyright (C) 2023, Hopsworks AB. All rights reserved
~
~ Hopsworks is free software: you can redistribute it and/or modify it under the terms of
~ the GNU Affero General Public License as published by the Free Software Foundation,
~ either version 3 of the License, or (at your option) any later version.
~
~ Hopsworks is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
~ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
~ PURPOSE. See the GNU Affero General Public License for more details.
~
~ You should have received a copy of the GNU Affero General Public License along with this program.
~ If not, see <https://www.gnu.org/licenses/>.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>hopsworks</artifactId>
<groupId>io.hops</groupId>
<version>3.7.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

<groupId>io.hops.hopsworks</groupId>
<artifactId>hopsworks-api-auth</artifactId>
<version>3.7.0-SNAPSHOT</version>
<packaging>ejb</packaging>
<description>Hopsworks API authentication filters</description>
<name>hopsworks-api-auth</name>

<properties>
<maven.deploy.skip>true</maven.deploy.skip>
</properties>

<dependencies>
<dependency>
<groupId>io.hops.hopsworks</groupId>
<artifactId>hopsworks-persistence</artifactId>
</dependency>
<dependency>
<groupId>io.hops.hopsworks</groupId>
<artifactId>hopsworks-rest-utils</artifactId>
<exclusions>
<exclusion>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.hops.hopsworks</groupId>
<artifactId>hopsworks-jwt</artifactId>
<type>ejb</type>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.16.0</version>
</dependency>
</dependencies>
<build>
<finalName>hopsworks-api-auth</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-ejb-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* This file is part of Hopsworks
* Copyright (C) 2023, Hopsworks AB. All rights reserved
*
* Hopsworks is free software: you can redistribute it and/or modify it under the terms of
* the GNU Affero General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*
* Hopsworks is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
*/

package io.hops.hopsworks.api.auth;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import io.hops.hopsworks.persistence.entity.util.Variables;
import io.hops.hopsworks.restutils.RESTLogLevel;

import javax.annotation.PostConstruct;
import javax.ejb.ConcurrencyManagement;
import javax.ejb.ConcurrencyManagementType;
import javax.ejb.Singleton;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

@Singleton
@ConcurrencyManagement(ConcurrencyManagementType.BEAN)
public class Configuration {

@PersistenceContext(unitName = "kthfsPU")
private EntityManager em;
private LoadingCache<AuthConfigurationKeys, String> configuration;

public enum AuthConfigurationKeys {

HOPSWORKS_REST_LOG_LEVEL("hopsworks_rest_log_level", RESTLogLevel.PROD.name());

private String key;
private String defaultValue;

AuthConfigurationKeys(String key, String defaultValue) {
this.key = key;
this.defaultValue = defaultValue;
}
}

@PostConstruct
public void init() {
configuration = CacheBuilder.newBuilder()
.maximumSize(100)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(new CacheLoader<AuthConfigurationKeys, String>() {
@Override
public String load(AuthConfigurationKeys k) throws Exception {
Variables var = em.find(Variables.class, k.key);
return var != null ? var.getValue() : k.defaultValue;
}
});
}

private String get(AuthConfigurationKeys key) {
try {
return configuration.get(key);
} catch (ExecutionException ex) {
return key.defaultValue;
}
}

public RESTLogLevel getLogLevel(AuthConfigurationKeys key) {
return RESTLogLevel.valueOf(get(key));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,42 +13,41 @@
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
*/
package io.hops.hopsworks.api.filter.util;
package io.hops.hopsworks.api.auth;

import javax.ws.rs.core.SecurityContext;
import java.security.Principal;

public class HopsworksSecurityContext implements SecurityContext {

private final String scheme;
private final Subject subject;

public HopsworksSecurityContext(Subject subject, String scheme) {
this.scheme = scheme;
this.subject = subject;
}

@Override
public Principal getUserPrincipal() {
if (this.subject == null) {
return null;
}
return () -> this.subject.getName();
}

@Override
public boolean isUserInRole(String role) {
if (this.subject.getRoles() != null && !this.subject.getRoles().isEmpty()) {
return this.subject.getRoles().contains(role);
}
return false;
}

@Override
public boolean isSecure() {
return "https".equals(this.scheme);
}

@Override
public String getAuthenticationScheme() {
return SecurityContext.BASIC_AUTH;
Expand Down
Loading

0 comments on commit b053bc3

Please sign in to comment.