본 포스팅은 CentOS 7 Linux에서 389 포트를 사용하는 LDAP 프로세스를 일반 계정으로 LISTEN 하는 방법에 대해 기술합니다.
독자 분들의 이해를 돕기 위해 Java 기반의 오픈소스 LDAP인 OpenDJ를 예시로 설명하였습니다.
LDAP이 아니더라도 Linux 일반 계정으로 잘 알려진 포트(Well-known Port)를 LISTEN 해야 하는 모든 어플리케이션에 공통적으로 적용됩니다.
IANA TCP/UDP 포트 분류 기준 권고안 (참고 링크)
– Well-known Port(0번 ~ 1023번): HTTP는 80번, SSH는 22번 포트와 같이 자주 사용되는 서비스에 기본 값으로 할당한 포트입니다.
– Registered Port(1024번 ~49151번): 주로 서버에서 프로세스에 포트 바인딩 시 사용하는 포트입니다.
– Dynamic Port(49152번 ~ 65535번): 주로 클라이언트가 서버와 소켓 통신을 할 때 서버에서 패킷을 수신을 받기 위해 임의로 부여되는 포트입니다.
CentOS 7 Linux는 root 계정이거나 CAP_NET_BIND_SERVICE 권한이 있는 경우에만 Well-known Port를 LISTEN 할 수 있도록 시스템 커널 변수를 통해 권한이 설정되어 있습니다.
우선 시스템에 기본 값으로 할당된 Well-known Port 범위를 확인합니다.
1 2 |
$ cat /proc/sys/net/ipv4/ip_unprivileged_port_start 1024 |
커널 변수 ip_unprivileged_port_start는 root 또는 CAP_NET_BIND_SERVICE 권한이 없어도 LISTEN 할 수 있는 첫 번째 포트를 지정합니다. 기본 값은 1024 입니다. root 계정이거나 CAP_NET_BIND_SERVICE 권한이 있는 경우 0번 ~ 1023번 포트를 LISTEN 할 수 있는 권한을 가지며, 일반 계정은 1024번 이후의 포트만 LISTEN 할 수 있습니다.
그렇기 때문에 일반 계정으로 LDAP 프로세스를 실행하기 위해서는 LDAP 기본 포트인 389 포트가 아닌 1024번 이후의 포트로 LDAP을 LISTEN 하도록 설정해야 합니다.
1 2 3 4 5 |
$ ps -ef | grep start-ds ldap 8966 1 33 16:54 pts/1 00:00:05 /usr/bin/java -server -Dorg.opends.server.scriptName=start-ds org.opends.server.core.DirectoryServer --configFile /home/ldap/OPENDJ/opendj/config/config.ldif $ netstat -lntp | grep 8966 tcp6 0 0 :::4444 :::* LISTEN 8966/java tcp6 0 0 :::1389 :::* LISTEN 8966/java |
본 예제에서는 ldap 계정을 통해 1389 포트로 LDAP을 LISTEN 하고 있습니다. 참고로 같이 조회된 4444 포트는 OpenDJ 내부 관리용 포트입니다.
현재 LISTEN 중인 1389 포트를 389 포트로 변경하고 ldap 계정으로 재 시작하면 권한이 없기 때문에 다음과 같은 메시지가 출력되며 강제로 프로세스 기동이 중단됩니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
$ cd /home/ldap/OPENDJ/opendj $ sed -i "s,ds-cfg-listen-port: 1389,ds-cfg-listen-port: 389," config/config.ldif $ bin/stop-ds --restart Stopping Server... [22/Oct/2021:16:37:00 +0900] category=PROTOCOL severity=NOTICE msgID=277 msg=Stopped listening for new connections on Administration Connector 0.0.0.0 port 4444 [22/Oct/2021:16:37:00 +0900] category=PROTOCOL severity=NOTICE msgID=277 msg=Stopped listening for new connections on LDAP Connection Handler 0.0.0.0 port 1389 [22/Oct/2021:16:37:03 +0900] category=BACKEND severity=NOTICE msgID=370 msg=The backend userRoot is now taken offline [22/Oct/2021:16:37:03 +0900] category=CORE severity=NOTICE msgID=203 msg=The Directory Server is now stopped [22/Oct/2021:16:50:07 +0900] category=com.forgerock.opendj.ldap.config.config severity=NOTICE msgID=571 msg=Loaded extension from file '/home/ldap/OPENDJ/opendj/lib/extensions/snmp-mib2605.jar' (build 4.4.7, revision f366bd375dbee4ab6a44f39889a93a35b22c0d4e) [22/Oct/2021:16:50:08 +0900] category=CORE severity=NOTICE msgID=134 msg=OpenDJ Server 4.4.7 (build 20200909182427, revision number f366bd375dbee4ab6a44f39889a93a35b22c0d4e) starting up [22/Oct/2021:16:50:09 +0900] category=JVM severity=NOTICE msgID=21 msg=Installation Directory: /home/ldap/OPENDJ/opendj [22/Oct/2021:16:50:09 +0900] category=JVM severity=NOTICE msgID=23 msg=Instance Directory: /home/ldap/OPENDJ/opendj [22/Oct/2021:16:50:09 +0900] category=JVM severity=NOTICE msgID=17 msg=JVM Information: 1.8.0_275-b01 by Red Hat, Inc., 64-bit architecture, 940703744 bytes heap size [22/Oct/2021:16:50:09 +0900] category=JVM severity=NOTICE msgID=18 msg=JVM Host: ldap.keis.or.kr, running Linux 4.18.0-240.1.1.el8_3.x86_64 amd64, 3889541120 bytes physical memory size, number of processors available 1 [22/Oct/2021:16:50:09 +0900] category=JVM severity=NOTICE msgID=19 msg=JVM Arguments: "-Dorg.opends.server.scriptName=start-ds" [22/Oct/2021:16:50:10 +0900] category=BACKEND severity=NOTICE msgID=513 msg=The database backend userRoot containing 0 entries has started [22/Oct/2021:16:50:10 +0900] category=EXTENSIONS severity=NOTICE msgID=221 msg=DIGEST-MD5 SASL mechanism using a server fully qualified domain name of: ldap.keis.or.kr [22/Oct/2021:16:50:10 +0900] category=PROTOCOL severity=ERROR msgID=432 msg=The LDAP connection handler defined in configuration entry cn=LDAP Connection Handler,cn=Connection Handlers,cn=config was unable to bind to 0.0.0.0:389: IOException(Address already in use) [22/Oct/2021:16:50:10 +0900] category=CORE severity=NOTICE msgID=139 msg=The Directory Server has sent an alert notification generated by class org.opends.server.core.DirectoryServer (alert type org.opends.server.DirectoryServerShutdown, alert ID org.opends.messages.core-141): The Directory Server has started the shutdown process. The shutdown was initiated by an instance of class org.opends.server.core.DirectoryServer and the reason provided for the shutdown was An error occurred while trying to start the Directory Server: ConfigException: An error occurred while trying to initialize a connection handler loaded from class org.forgerock.opendj.reactive.LDAPConnectionHandler2 with the information in configuration entry cn=LDAP Connection Handler,cn=Connection Handlers,cn=config: InitializationException: The LDAP connection handler defined in configuration entry cn=LDAP Connection Handler,cn=Connection Handlers,cn=config was unable to bind to 0.0.0.0:389: IOException(Address already in use) (LDAPConnectionHandler2.java:520 LDAPConnectionHandler2.java:103 ConnectionHandlerConfigManager.java:311 ConnectionHandlerConfigManager.java:244 DirectoryServer.java:1753 DirectoryServer.java:1483 DirectoryServer.java:5041). This connection handler will be disabled (ConnectionHandlerConfigManager.java:319 ConnectionHandlerConfigManager.java:244 DirectoryServer.java:1753 DirectoryServer.java:1483 DirectoryServer.java:5041) [22/Oct/2021:16:50:10 +0900] category=BACKEND severity=NOTICE msgID=370 msg=The backend userRoot is now taken offline [22/Oct/2021:16:50:10 +0900] category=CORE severity=NOTICE msgID=203 msg=The Directory Server is now stopped |
LDAP을 389 포트로 기동시킬 수 있는 몇 가지의 해결 방안이 있으며 상황에 따라 적용하시면 됩니다.
1. root 계정 사용
2. firewalld 포트포워딩 설정
3. 커널 변수 값 변경
4. capabilities 설정
1. root 계정 사용
말 그대로 root 계정의 사용이 가능한 경우 root 계정으로 LDAP 프로세스를 기동하는 것을 의미합니다. 다만 무분별한 root의 사용은 보안상 다양한 취약점을 발생시킬 수 있으므로 권장하지 않는 방법입니다.
2. firewalld 포트포워딩 설정
CentOS 7 네트워크 방화벽인 firewalld를 통한 포트포워딩 설정을 수행합니다. 단, 시스템 내부에 LISTEN 되는 포트를 변경하는 방법은 아니므로 참고해주시기 바랍니다.
보안상으론 좋은 방법이지만 별도의 방화벽 관리가 필요하기 때문에 만약 방화벽 자체에 문제가 생길 경우 LDAP 서비스가 안 되는 상황이 생길 수 있습니다.
또한 LDAP 서비스 관리 시 내부와 외부 포트가 서로 다르기 때문에 LDAP 복제 구성을 하거나 시스템 내/외부적으로 LDAP 포트를 참조하는 프로그램을 사용하는 등의 일부 상황에서 제약 사항이 발생할 수도 있습니다.
우선 root 계정으로 시스템에서 firewalld 서비스가 실행 중인지 확인합니다.
1 2 3 4 5 |
$ systemctl status firewalld ● firewalld.service - firewalld - dynamic firewall daemon Loaded: loaded (/usr/lib/systemd/system/firewalld.service; disabled; vendor preset: enabled) Active: inactive (dead) Docs: man:firewalld(1) |
현재 실행 중이 아닌 경우 firewalld 서비스를 실행합니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$ systemctl start firewalld ● firewalld.service - firewalld - dynamic firewall daemon Loaded: loaded (/usr/lib/systemd/system/firewalld.service; disabled; vendor preset: enabled) Active: active (running) since Fri 2021-10-22 17:40:58 KST; 4s ago Docs: man:firewalld(1) Main PID: 9959 (firewalld) Tasks: 2 (limit: 23528) Memory: 21.5M CGroup: /system.slice/firewalld.service └─9959 /usr/libexec/platform-python -s /usr/sbin/firewalld --nofork --nopid Oct 22 17:40:58 keis-ldap systemd[1]: Starting firewalld - dynamic firewall daemon... Oct 22 17:40:58 keis-ldap systemd[1]: Started firewalld - dynamic firewall daemon. |
방화벽 정책은 아래의 명령어를 통해 확인하실 수 있습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$ firewall-cmd --list-all public (active) target: default icmp-block-inversion: no interfaces: ens33 sources: services: cockpit dhcpv6-client ssh ports: protocols: masquerade: no forward-ports: source-ports: icmp-blocks: rich rules: |
포트포워딩 설정을 진행합니다.
1 2 3 4 |
$ firewall-cmd --permanent --add-forward-port=port=389:proto=tcp:toport=1389 success $ firewall-cmd --reload success |
포트포워딩 설정을 진행하면 시스템 외부의 사용자는 389 포트로 LDAP에 접속할 수 있습니다. 다만 firewalld 서비스가 실행 상태가 아니면 포트포워딩을 하지 않기 때문에 시스템에 LISTEN 중인 LDAP 포트로 접속해야 하는 상황이 생길 수 있으므로 관리 시 주의가 필요합니다.
3. 커널 변수 값 변경
커널 변수 ip_unprivileged_port_start 값을 0으로 변경하면 Well-known Port의 LISTEN이 가능합니다.
1 2 |
$ sysctl net.ipv4.ip_unprivileged_port_start=0 net.ipv4.ip_unprivileged_port_start = 0 |
쉬운 방법이지만 시스템의 커널 변수를 변경하면 보안에 악영향을 줄 수 있습니다. 공격자에게 다양한 공격 수단을 쥐어주는 행위이므로 권장하지 않는 방법입니다.
4. capabilities 설정
가장 안전한 방법은 root 계정으로 Java에 CAP_NET_BIND_SERVICE 권한을 부여하고 일반 계정으로 LDAP을 기동하는 방법입니다. OpenDJ는 JVM에서 동작하므로 실제로 포트 바인딩을 수행하는 Java 실행 파일에 권한을 부여해야 합니다.
1 |
$ setcap 'cap_net_bind_service+ep' /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.275.b01-1.el8_3.x86_64/jre/bin/java |
Java 실행 파일에 권한이 부여되었는지 확인합니다.
1 2 |
$ getcap /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.275.b01-1.el8_3.x86_64/jre/bin/java /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.275.b01-1.el8_3.x86_64/jre/bin/java = cap_net_bind_service+ep |
setcap 명령어를 통해 권한을 부여한 이후 LDAP 기동 시 아래와 유사한 에러 메시지가 발생할 수 있습니다.
1 2 3 4 5 6 7 8 9 10 11 12 |
$ cd /home/ldap/OPENDJ/opendj $ bin/start-ds ERROR: The detected Java version could not be used with the set of Java arguments -server. The detected Java binary is: /bin/java You must specify the path to a valid Java 7 or higher version. The procedure to follow is to set the environment variable OPENDJ_JAVA_HOME to the root of a valid Java 7 installation. If you want to have specific Java settings for each command line you must edit the properties file specifying the Java binary and/or the Java arguments for each command line. The Java properties file is located in: /home/ldap/OPENDJ/opendj/config/java.properties. |
이 경우 터미널에서 java를 실행하면 아래의 에러 메시지가 출력되는 것을 확인할 수 있습니다.
1 2 |
$ java java: error while loading shared libraries: libjli.so: cannot open shared object file: No such file or directory |
/etc/ld.so.conf.d/java.conf 파일에 libjli.so 파일의 절대 경로를 입력하여 Java가 해당 라이브러리를 참조할 수 있도록 조치합니다.
1 2 |
$ cat /etc/ld.so.conf.d/java.conf /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.275.b01-1.el8_3.x86_64/jre/lib/amd64/jli |
이제 다시 일반 계정으로 LDAP을 기동하면 정상적으로 기동이 수행됩니다.