-
Notifications
You must be signed in to change notification settings - Fork 472
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement ipWhitelistUserLogin Plugin
- Loading branch information
Showing
4 changed files
with
230 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
Artifactory IP-based User Login Whitelisting | ||
===================================================== | ||
|
||
This plugin allows to limit user login for specific users to a specific list of IP addresses. | ||
|
||
1. If a user is not listed in the configuration, the login is not restricted, and it is the default as it would be without using this plugin | ||
2. The configuration allows to specify for a specific username the allowed IP address and the Group to be assigned when logging in. | ||
3. If a user is listed in the configuration, all given IP addresses are checked. This is a simple 'starts With' check. | ||
1. If the IP address is not included in the list, the User Login is prevented | ||
2. If the IP address is in the list, the User gets assigned the given Group (if it exists), and can log in | ||
|
||
## Configuration | ||
|
||
The configuration [ipWhitelistUserLogin.json](ipWhitelistUserLogin.json) is used to configure | ||
the plugin. | ||
|
||
The `assignGroup` option is optional, but should be used for security reasons! | ||
|
||
I.e., use this specific group to define permissions for the user, do not use the username directly. | ||
If the plugin is somehow disabled for whatever reason, the user may log in from any IP and automatically has | ||
the given rights. | ||
If you use a dedicated group, and this Plugin is somehow disabled, the user is not assigned to the group, | ||
and therefore does not have any rights. | ||
|
||
|
||
Based on the idea given here: | ||
https://www.jfrog.com/jira/browse/RTFACT-9157 | ||
|
||
Installation | ||
------------ | ||
|
||
To install this plugin: | ||
|
||
1. Place the configuration file | ||
`ipWhitelistUserLogin.json` file in the | ||
`${ARTIFACTORY_HOME}/etc/plugins` directory and adapt it to your needs. | ||
2. Create any groups you configure in the config, and adapt the permissions. | ||
3. Place the `ipWhitelistUserLogin.groovy` file in the | ||
`${ARTIFACTORY_HOME}/etc/plugins` directory. | ||
4. You are done. Verify that the plugin properly works by trying to log in from a non-allowed IP. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
/* | ||
* Copyright (C) 2014 JFrog 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. | ||
*/ | ||
|
||
/** | ||
* | ||
* @author Stefan Profanter | ||
* @since 21/04/22 | ||
*/ | ||
import org.artifactory.security.RealmPolicy | ||
|
||
import groovy.json.JsonSlurper | ||
import groovy.transform.Field | ||
|
||
|
||
@Field final String CONFIG_FILE_PATH = "plugins/ipWhitelistUserLogin.json" | ||
def configFile = new File(ctx.artifactoryHome.etcDir, CONFIG_FILE_PATH) | ||
|
||
def config = null | ||
|
||
if ( configFile.exists() ) { | ||
|
||
config = new JsonSlurper().parse(configFile.toURL()) | ||
log.info "Loaded ipWhitelistUserLogin config for users: $config.users" | ||
|
||
} else { | ||
log.error "Config file $configFile is missing!" | ||
} | ||
|
||
|
||
realms { | ||
ipWhitelistRealm(realmPolicy: RealmPolicy.ADDITIVE) { | ||
authenticate { username, credentials -> | ||
if (config == null) { | ||
log.error "Config file $configFile is missing!" | ||
return true | ||
} | ||
|
||
String ip = request.getClientAddress() | ||
|
||
|
||
if (!config.users.containsKey(username)) { | ||
return true | ||
} | ||
|
||
for (allow_ip in config.users[username]['allow']) { | ||
if (ip.startsWith("$allow_ip")){ | ||
if (config.users[username].containsKey("assignGroup")) { | ||
String groupName = config.users[username]['assignGroup'] | ||
log.info "${username} is trying to login from whitelisted IP, assigning the user to ${groupName}" | ||
groups += groupName | ||
} else { | ||
log.info "User: ${username} with IP ${ip} matches allowed IP ${allow_ip}" | ||
} | ||
return true | ||
} | ||
} | ||
|
||
log.warn "User '${username}' login not allowed. IP address ${ip} not whitelisted" | ||
return false | ||
} | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
"users": { | ||
"alice": { | ||
"allow": ["172.21.0.", "127.0.0."], | ||
"assignGroup": "ip-allowed" | ||
}, | ||
"bob": { | ||
"allow": ["172.21.0.", "127.0.0."], | ||
"assignGroup": "ip-allowed-bob" | ||
} | ||
} | ||
} |
102 changes: 102 additions & 0 deletions
102
security/ipWhitelistUserLogin/ipWhitelistUserLoginTest.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import org.artifactory.api.repo.storage.RepoStorageSummaryInfo | ||
import org.jfrog.artifactory.client.Artifactory | ||
import org.jfrog.artifactory.client.RepositoryHandle | ||
import org.jfrog.artifactory.client.model.LightweightRepository | ||
import org.jfrog.artifactory.client.model.LocalRepository | ||
import org.jfrog.artifactory.client.model.PackageType | ||
import org.jfrog.artifactory.client.model.Privilege | ||
import org.jfrog.artifactory.client.model.Repository | ||
import org.jfrog.artifactory.client.model.RepositoryType | ||
import org.jfrog.artifactory.client.model.builder.GroupBuilder | ||
import org.jfrog.artifactory.client.model.builder.LocalRepositoryBuilder | ||
import org.jfrog.artifactory.client.model.repository.settings.ConanRepositorySettings | ||
import org.jfrog.artifactory.client.model.repository.settings.RepositorySettings | ||
import org.jfrog.artifactory.client.model.repository.settings.impl.ConanRepositorySettingsImpl | ||
import org.jfrog.artifactory.client.model.repository.settings.impl.MavenRepositorySettingsImpl | ||
import spock.lang.Specification | ||
|
||
import org.jfrog.artifactory.client.ArtifactoryClientBuilder | ||
import org.jfrog.artifactory.client.model.builder.UserBuilder | ||
|
||
class IpWhitelistUserLoginTest extends Specification { | ||
|
||
def grantRepoReadPermissionToGroup (Artifactory artifactory, String permissionName, String groupName, String repoKey) { | ||
def principal = artifactory.security().builders().principalBuilder() | ||
.name(groupName) | ||
.privileges(Privilege.READ) | ||
.build() | ||
def principals = artifactory.security().builders().principalsBuilder() | ||
.groups(principal) | ||
.build() | ||
def permission = artifactory.security().builders().permissionTargetBuilder() | ||
.name(permissionName) | ||
.repositories(repoKey) | ||
.principals(principals) | ||
.build() | ||
artifactory.security().createOrReplacePermissionTarget(permission) | ||
} | ||
|
||
def 'ip whitelist user login test'() { | ||
setup: | ||
def baseurl1 = 'http://localhost:8082/artifactory' | ||
def password = "password" | ||
def artifactory = ArtifactoryClientBuilder.create().setUrl(baseurl1).setUsername('admin').setPassword(password).build() | ||
def artifactory_bob = ArtifactoryClientBuilder.create().setUrl(baseurl1).setUsername('bob').setPassword(password).build() | ||
|
||
when: | ||
|
||
/* | ||
1. Create a group (same as in the config, which will be assigned by the plugin) | ||
2. Create a repository: conan-test | ||
3. Allow the group to read that repository | ||
4. Create the user `bob` (without assigning the group) | ||
5. Then test that user `bob` gets the group assigned by the plugin, by verifying that it can see the repo | ||
*/ | ||
|
||
GroupBuilder groupBilder = artifactory.security().builders().groupBuilder() | ||
def ip_allowed__group = groupBilder | ||
.name("ip-allowed-bob") | ||
.adminPrivileges(false) | ||
.autoJoin(false).build(); | ||
artifactory.security().createOrUpdateGroup(ip_allowed__group) | ||
|
||
LocalRepositoryBuilder localRepositoryBuilder = artifactory.repositories().builders().localRepositoryBuilder() | ||
localRepositoryBuilder.key("conan-test").repositorySettings(new ConanRepositorySettingsImpl()) | ||
artifactory.repositories().create(0, localRepositoryBuilder.build()) | ||
artifactory.security().builders().permissionTargetBuilder() | ||
|
||
grantRepoReadPermissionToGroup(artifactory, "conan-test-read", "ip-allowed-bob", "conan-test") | ||
|
||
UserBuilder userBuilder = artifactory.security().builders().userBuilder() | ||
def user1 = userBuilder.name("bob") | ||
.email("[email protected]") | ||
.admin(false) | ||
.profileUpdatable(false) | ||
.password(password) | ||
.build(); | ||
artifactory.security().createOrUpdate(user1) | ||
|
||
then: | ||
|
||
def repos_list = artifactory_bob.repositories().list() | ||
|
||
boolean repo_found = false | ||
|
||
for (repo in repos_list) { | ||
if (repo.key == "conan-test") { | ||
repo_found = true | ||
break | ||
} | ||
} | ||
|
||
repo_found | ||
|
||
cleanup: | ||
String result3 = artifactory.repository("conan-test").delete() | ||
String result2 = artifactory.security().deleteGroup("ip-allowed-bob") | ||
String result4 = artifactory.security().deletePermissionTarget("conan-test-read") | ||
String result1 = artifactory.security().deleteUser("bob") | ||
} | ||
} |