Skip to content

Commit

Permalink
feat: implement ipWhitelistUserLogin Plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
Pro committed Apr 22, 2022
1 parent 8acc211 commit c51410b
Show file tree
Hide file tree
Showing 4 changed files with 230 additions and 0 deletions.
40 changes: 40 additions & 0 deletions security/ipWhitelistUserLogin/README.md
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.
76 changes: 76 additions & 0 deletions security/ipWhitelistUserLogin/ipWhitelistUserLogin.groovy
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
}
}
}

12 changes: 12 additions & 0 deletions security/ipWhitelistUserLogin/ipWhitelistUserLogin.json
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 security/ipWhitelistUserLogin/ipWhitelistUserLoginTest.groovy
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")
}
}

0 comments on commit c51410b

Please sign in to comment.