Skip to content

Commit a003655

Browse files
committed
feat: implement ipWhitelistUserLogin Plugin
1 parent 8acc211 commit a003655

File tree

4 files changed

+229
-0
lines changed

4 files changed

+229
-0
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
Artifactory IP-based User Login Whitelisting
2+
=====================================================
3+
4+
This plugin allows to limit user login for specific users to a specific list of IP addresses.
5+
6+
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
7+
2. The configuration allows to specify for a specific username the allowed IP address and the Group to be assigned when logging in.
8+
3. If a user is listed in the configuration, all given IP addresses are checked. This is a simple 'starts With' check.
9+
1. If the IP address is not included in the list, the User Login is prevented
10+
2. If the IP address is in the list, the User gets assigned the given Group (if it exists), and can log in
11+
12+
## Configuration
13+
14+
The configuration [ipWhitelistUserLogin.json](ipWhitelistUserLogin.json) is used to configure
15+
the plugin.
16+
17+
The `assignGroup` option is optional, but should be used for security reasons!
18+
19+
I.e., use this specific group to define permissions for the user, do not use the username directly.
20+
If the plugin is somehow disabled for whatever reason, the user may log in from any IP and automatically has
21+
the given rights.
22+
If you use a dedicated group, and this Plugin is somehow disabled, the user is not assigned to the group,
23+
and therefore does not have any rights.
24+
25+
26+
Based on the idea given here:
27+
https://www.jfrog.com/jira/browse/RTFACT-9157
28+
29+
Installation
30+
------------
31+
32+
To install this plugin:
33+
34+
1. Place the configuration file
35+
`ipWhitelistUserLogin.json` file in the
36+
`${ARTIFACTORY_HOME}/etc/plugins` directory and adapt it to your needs.
37+
2. Create any groups you configure in the config, and adapt the permissions.
38+
3. Place the `ipWhitelistUserLogin.groovy` file in the
39+
`${ARTIFACTORY_HOME}/etc/plugins` directory.
40+
4. You are done. Verify that the plugin properly works by trying to log in from a non-allowed IP.
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Copyright (C) 2014 JFrog Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/**
18+
*
19+
* @author Stefan Profanter
20+
* @since 21/04/22
21+
*/
22+
import org.artifactory.security.RealmPolicy
23+
24+
import groovy.json.JsonSlurper
25+
import groovy.transform.Field
26+
27+
28+
@Field final String CONFIG_FILE_PATH = "plugins/ipWhitelistUserLogin.json"
29+
def configFile = new File(ctx.artifactoryHome.etcDir, CONFIG_FILE_PATH)
30+
31+
def config = null
32+
33+
if ( configFile.exists() ) {
34+
35+
config = new JsonSlurper().parse(configFile.toURL())
36+
log.info "Loaded ipWhitelistUserLogin config for users: $config.users"
37+
38+
} else {
39+
log.error "Config file $configFile is missing!"
40+
}
41+
42+
43+
realms {
44+
ipWhitelistRealm(realmPolicy: RealmPolicy.ADDITIVE) {
45+
authenticate { username, credentials ->
46+
if (config == null) {
47+
log.error "Config file $configFile is missing!"
48+
return true
49+
}
50+
51+
String ip = request.getClientAddress()
52+
53+
if (!config.users.containsKey(username)) {
54+
return true
55+
}
56+
57+
for (allow_ip in config.users[username]['allow']) {
58+
if (ip.startsWith("$allow_ip")){
59+
if (config.users[username].containsKey("assignGroup")) {
60+
String groupName = config.users[username]['assignGroup']
61+
log.info "${username} is trying to login from whitelisted IP, assigning the user to ${groupName}"
62+
groups += groupName
63+
} else {
64+
log.info "User: ${username} with IP ${ip} matches allowed IP ${allow_ip}"
65+
}
66+
return true
67+
}
68+
}
69+
70+
log.warn "User '${username}' login not allowed. IP address ${ip} not whitelisted"
71+
return false
72+
}
73+
}
74+
}
75+
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"users": {
3+
"alice": {
4+
"allow": ["172.21.0.", "127.0.0."],
5+
"assignGroup": "ip-allowed"
6+
},
7+
"bob": {
8+
"allow": ["172.21.0.", "127.0.0."],
9+
"assignGroup": "ip-allowed-bob"
10+
}
11+
}
12+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import org.artifactory.api.repo.storage.RepoStorageSummaryInfo
2+
import org.jfrog.artifactory.client.Artifactory
3+
import org.jfrog.artifactory.client.RepositoryHandle
4+
import org.jfrog.artifactory.client.model.LightweightRepository
5+
import org.jfrog.artifactory.client.model.LocalRepository
6+
import org.jfrog.artifactory.client.model.PackageType
7+
import org.jfrog.artifactory.client.model.Privilege
8+
import org.jfrog.artifactory.client.model.Repository
9+
import org.jfrog.artifactory.client.model.RepositoryType
10+
import org.jfrog.artifactory.client.model.builder.GroupBuilder
11+
import org.jfrog.artifactory.client.model.builder.LocalRepositoryBuilder
12+
import org.jfrog.artifactory.client.model.repository.settings.ConanRepositorySettings
13+
import org.jfrog.artifactory.client.model.repository.settings.RepositorySettings
14+
import org.jfrog.artifactory.client.model.repository.settings.impl.ConanRepositorySettingsImpl
15+
import org.jfrog.artifactory.client.model.repository.settings.impl.MavenRepositorySettingsImpl
16+
import spock.lang.Specification
17+
18+
import org.jfrog.artifactory.client.ArtifactoryClientBuilder
19+
import org.jfrog.artifactory.client.model.builder.UserBuilder
20+
21+
class IpWhitelistUserLoginTest extends Specification {
22+
23+
def grantRepoReadPermissionToGroup (Artifactory artifactory, String permissionName, String groupName, String repoKey) {
24+
def principal = artifactory.security().builders().principalBuilder()
25+
.name(groupName)
26+
.privileges(Privilege.READ)
27+
.build()
28+
def principals = artifactory.security().builders().principalsBuilder()
29+
.groups(principal)
30+
.build()
31+
def permission = artifactory.security().builders().permissionTargetBuilder()
32+
.name(permissionName)
33+
.repositories(repoKey)
34+
.principals(principals)
35+
.build()
36+
artifactory.security().createOrReplacePermissionTarget(permission)
37+
}
38+
39+
def 'ip whitelist user login test'() {
40+
setup:
41+
def baseurl1 = 'http://localhost:8082/artifactory'
42+
def password = "password"
43+
def artifactory = ArtifactoryClientBuilder.create().setUrl(baseurl1).setUsername('admin').setPassword(password).build()
44+
def artifactory_bob = ArtifactoryClientBuilder.create().setUrl(baseurl1).setUsername('bob').setPassword(password).build()
45+
46+
when:
47+
48+
/*
49+
50+
1. Create a group (same as in the config, which will be assigned by the plugin)
51+
2. Create a repository: conan-test
52+
3. Allow the group to read that repository
53+
4. Create the user `bob` (without assigning the group)
54+
5. Then test that user `bob` gets the group assigned by the plugin, by verifying that it can see the repo
55+
56+
*/
57+
58+
GroupBuilder groupBilder = artifactory.security().builders().groupBuilder()
59+
def ip_allowed__group = groupBilder
60+
.name("ip-allowed-bob")
61+
.adminPrivileges(false)
62+
.autoJoin(false).build();
63+
artifactory.security().createOrUpdateGroup(ip_allowed__group)
64+
65+
LocalRepositoryBuilder localRepositoryBuilder = artifactory.repositories().builders().localRepositoryBuilder()
66+
localRepositoryBuilder.key("conan-test").repositorySettings(new ConanRepositorySettingsImpl())
67+
artifactory.repositories().create(0, localRepositoryBuilder.build())
68+
artifactory.security().builders().permissionTargetBuilder()
69+
70+
grantRepoReadPermissionToGroup(artifactory, "conan-test-read", "ip-allowed-bob", "conan-test")
71+
72+
UserBuilder userBuilder = artifactory.security().builders().userBuilder()
73+
def user1 = userBuilder.name("bob")
74+
75+
.admin(false)
76+
.profileUpdatable(false)
77+
.password(password)
78+
.build();
79+
artifactory.security().createOrUpdate(user1)
80+
81+
then:
82+
83+
def repos_list = artifactory_bob.repositories().list()
84+
85+
boolean repo_found = false
86+
87+
for (repo in repos_list) {
88+
if (repo.key == "conan-test") {
89+
repo_found = true
90+
break
91+
}
92+
}
93+
94+
repo_found
95+
96+
cleanup:
97+
String result3 = artifactory.repository("conan-test").delete()
98+
String result2 = artifactory.security().deleteGroup("ip-allowed-bob")
99+
String result4 = artifactory.security().deletePermissionTarget("conan-test-read")
100+
String result1 = artifactory.security().deleteUser("bob")
101+
}
102+
}

0 commit comments

Comments
 (0)