亚马逊AWS官方博客

基于 TLS 1.2 TLS 1.3 的 SAML ADFS 实现控制台及 Redshift 用户的安全单点登录(三)

客户端

前面两章分别完成了 Windows 服务器以及亚马逊云平台的安装配置,本章我们将在另一台 EC2 实例上部署客户端应用程序。相比常规用户名密码连接方式,IAM 认证方式需要客户端配置额外的属性(Properties),并且一些属性值中国区与其它区不同,因此用 IAM 方式连接 Redshift 数据库在客户案例中占很大的比重。本章将介绍 ODBC 以及 JDBC 常用工具的配置,最后通过一段代码展示如何在 java 中设置这些属性实现免用户连接 Redshift。

1. 网络准备

将 DNS 指向域服务器,由于我们没有配置 DNS 服务,因此需要在客户端本地 hosts 里添加 IP 记录

用管理员身份打开文本编辑器,然后在 hosts 文件中添加以下行

type C:\Windows\System32\drivers\etc\hosts
……

10.0.47.17     dc   dc.adfs.cn    adfs.cn

2. PowerShell 运行以下命令添加注册表键以便 .NET Framework 启用 TLS 1.2/TLS 1.3

New-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\.NETFramework\v2.0.50727' -Name 'SystemDefaultTlsVersions' -Value 1 -PropertyType dword -Force

New-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\.NETFramework\v2.0.50727' -Name 'SchUseStrongCrypto' -Value 1 -PropertyType dword -Force

New-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\.NETFramework\v4.0.30319' -Name 'SystemDefaultTlsVersions' -Value 1 -PropertyType dword -Force

New-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\.NETFramework\v4.0.30319' -Name 'SchUseStrongCrypto' -Value 1 -PropertyType dword -Force

3. 将客户端加入到域

以下命令执行之后会出现用户名密码弹窗. 需填写域管理员 adfs.cn\administrator 及其密码

Add-Computer -DomainName adfs.cn -Restart

重启之后先用域管理员登录然后把 bob 加入到本地管理员组方便后续测试

Add-LocalGroupMember -Group "Administrators" -Member adfs.cn\bob

4. 导入证书,安装 Firefox 浏览器

将服务器配置过程中创建的自签名证书文件 adfs.cn.pfx 复制到 C:\Users\bob\Documents

$secure_pw = ConvertTo-SecureString "MyPassword" -AsPlainText -Force

Import-PfxCertificate -Password $secure_pw -CertStoreLocation Cert:\LocalMachine\Root -FilePath C:\Users\bob\Documents\adfs.cn.pfx

$firefoxUrl = "https://download.mozilla.org/?product=firefox-latest&os=win64&lang=en-US"
$destination = "$env:TEMP\firefox_installer.exe"
Invoke-WebRequest -Uri $firefoxUrl -OutFile $destination;Start-Process -FilePath "$env:TEMP\firefox_installer.exe" -ArgumentList "/S" -Wait

5. 使用 bob 域账号登录亚马逊控制台

https://dc.adfs.cn/adfs/ls/IdpInitiatedSignOn.aspx

我们在 ADFS 配置章节已经验证 bob 域账号可以登录到亚马逊控制台。从客户端再次验证旨在确认与服务器的通讯。

6. 准备 Redshift 环境

使用 Redshift 主账号登录到数据库 mydb。创建以下 schema,group 及表。

我们给 DB GROUP dev 赋予相应的权限。当应用程序通过 IAM 连接到 Redshift 时,用户将从这个 GROUP 继承 schema marketing 的权限。

mydb=# create group dev;
CREATE GROUP
Time: 318.659 ms
mydb=# CREATE SCHEMA marketing;
CREATE SCHEMA
Time: 338.713 ms
mydb=# CREATE TABLE IF NOT EXISTS marketing.employee
(id INTEGER
,name CHAR(25)
,age INTEGER
,city VARCHAR(152)
) DISTSTYLE AUTO;
CREATE TABLE
Time: 210.544 ms
mydb=# INSERT INTO marketing.employee VALUES(1, 'Bob', 20, 'Beijing');
INSERT 0 1
Time: 8055.116 ms
dev=# ALTER DEFAULT PRIVILEGES IN SCHEMA marketing GRANT SELECT ON TABLES TO GROUP dev;
ALTER DEFAULT PRIVILEGES
Time: 279.615 ms
mydb=# GRANT USAGE on SCHEMA marketing to GROUP dev;
GRANT
Time: 423.918 ms
mydb=# GRANT SELECT on ALL TABLES in SCHEMA marketing to GROUP dev;
GRANT
Time: 320.783 ms
mydb=# select * from marketing.employee;
id |           name            | age |  city
----+---------------------------+-----+---------
  1 | Bob                       |  20 | Beijing
(1 row)

7. 配置 ODBC

亚马逊官方网站下载 AmazonRedshiftODBC64 驱动然后使用默认选项安装。

以管理员身份打开 ODBC Data Sources (64-bit) 工具,添加 System DNS,选择名为“Amazon Redshift ODBC Driver(x64)”的驱动进入以下界面:

Data Source Name: redshift-saml
Server: cluster-identifier.cm4u4abcde6.cn-northwest-1.redshift.amazonaws.com.cn
Port: 5439
Database: mydb
Auth Type: Identity Provider: AD FS
User Name: adfs.cn\bob
Passrword: MyPassword123
Cluster ID: cluster-identifier
Region: cn-northwest-1
Idp Host: dc.adfs.cn
Idp Port: 443
Login To RP: urn:amazon:webservices:cn-north-1
SSL Insecure – Checked

注:以上参数根据实际配置填写。其中 Login To RP 为固定值,中国区无论哪个 Region 都填 urn:amazon:webservices:cn-north-1

用 Powershell 命令验证是否可以通过这个 DSN 读取表中的数据:

PS C:\Users\bob> $conn = New-Object System.Data.Odbc.OdbcConnection("DSN=redshift-saml")
PS C:\Users\bob> $conn.open()
PS C:\Users\bob> $cmd = $conn.CreateCommand()
PS C:\Users\bob> $cmd.CommandText = "SELECT * FROM marketing.employee"
PS C:\Users\bob> $reader = $cmd.ExecuteReader()
PS C:\Users\bob> $reader.Read()
True
PS C:\Users\bob> $reader[0]
1
PS C:\Users\bob> $reader[1]
Bob
PS C:\Users\bob> $reader.Close()
PS C:\Users\bob> $conn.Close()

8. 配置 JDBC – DBeaver

下载安装 java jdk17 并使用默认选项安装

亚马逊官方网站下载 redshift-jdbc42-2.1.0.26 驱动,解压至 C:\redshift-jdbc42-2.1.0.26 备用

下载 DBeaver,本例使用 23.3 社区版,使用默认选项安装

从菜单中打开驱动管理器以修改 Redshift 驱动的默认参数

URL Template: jdbc:redshift:iam://{host}:{port}/{database}

将默认 Lib 删除,Add Folder 添加 Redshift JDBC 驱动 C:\redshift-jdbc42-2.1.0.26

点击 Find Class 确保 com.amazon.redshift.jdbc42.Driver 被发现。期间如果有 sdk 依赖,请点击 Download

偶有 certificate 导致网络无法访问的情况可以通过取消勾选 Preferences 中的 Windows trust store 来解决

准备好驱动之后,点击菜单中的“创建新数据库连接”并选择“Redshift”进入以下界面:

URL - Checked
URL: jdbc:redshift:iam//cluster-identifier.xxx.cn-northwest-1.redshift.amazonaws.com.cn:5439/mydb
Username: 域账号 e.g. adfs.cn\bob
Password: 域账号的密码

点击 Driver properties 添加以下条目:

IdP_Host		dc.adfs.cn
IdP_Port		443
Plugin_Name	com.amazon.redshift.plugin.AdfsCredentialsProvider
loginToRp	urn:amazon:webservices:cn-north-1
Region		cn-northwest-1
SSL_Insecure	true

9. JDBC – SQL Workbench/J

从官方下载 SQL Workbench/J 软件包,本文使用 Build 128 及 jre_win64

解压至 C:\Workbench-Build128,依赖包 JRE 解压至 C:\Workbench-Build128\jre_win64

首次运行 SQLWorkbench64 需指定 Java home。选择以上解压路径之后进入 Connection profile配置界面。如下图所示:

点击 Manage Drivers,创建名为 AmazonRedshiftIAM 的驱动,各字段如下:

Name: AmazonRedshiftIAM
Library: C:\redshift-jdbc42-2.1.0.26\redshift-jdbc42-2.1.0.26.jar
Classname: com.amazon.redshift.jdbc42.Driver
Sample URL: jdbc:redshift:iam://{host}:{port}/{database}

使用新创建的驱动添加 Connection profile,如下图所示:

点击 Extended Properties 添加以下条目:

IdP_Host		    dc.adfs.cn
IdP_Port		    443
Plugin_Name		com.amazon.redshift.plugin.AdfsCredentialsProvider
loginToRp		urn:amazon:webservices:cn-north-1
Region		 	cn-northwest-1
SSL_Insecure	    true

10. JDBC SDK – Java

下载 Apache Maven,本文使用 3.8.8

解压至 C:\apache-maven-3.8.8

以管理员身份运行以下 PowerShell 命令添加 Maven 环境变量:

[Environment]::SetEnvironmentVariable("MAVEN_HOME", "C:\apache-maven-3.8.8", "Machine") 
[Environment]::SetEnvironmentVariable("Path", $env:Path + ";C:\apache-maven-3.8.8\bin", "Machine")

后续 Java 代码调试将通过 cmd 命令行而不是 PowerShell:

Microsoft Windows [Version 10.0.20348.2322]
(c) Microsoft Corporation. All rights reserved.

C:\>java -version
java version "17.0.10" 2024-01-16 LTS
Java(TM) SE Runtime Environment (build 17.0.10+11-LTS-240)
Java HotSpot(TM) 64-Bit Server VM (build 17.0.10+11-LTS-240, mixed mode, sharing)

C:\>mvn -version
Apache Maven 3.8.8 (4c87b05d9aedce574290d1acc98575ed5eb6cd39)
Maven home: C:\apache-maven-3.8.8
Java version: 17.0.10, vendor: Oracle Corporation, runtime: C:\Program Files\Java\jdk-17
Default locale: en_US, platform encoding: Cp1252
OS name: "windows server 2022", version: "10.0", arch: "amd64", family: "windows"

C:\>mkdir rs.jdbc

C:\>cd rs.jdbc

C:\rs.jdbc>mvn -B archetype:generate -DgroupId=rs.jdbc -DartifactId=jdbciam -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4

[INFO] Scanning for projects...
[INFO] Downloading from central: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-clean-plugin/2.5/maven-clean-plugin-2.5.pom
[INFO] Downloaded from central: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-clean-plugin/2.5/maven-clean-plugin-2.5.pom (3.9 kB at 4.0 kB/s)
.
.
.
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Old (1.x) Archetype: maven-archetype-quickstart:1.0
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: basedir, Value: C:\rs.jdbc
[INFO] Parameter: package, Value: rs.jdbc
[INFO] Parameter: groupId, Value: rs.jdbc
[INFO] Parameter: artifactId, Value: jdbciam
[INFO] Parameter: packageName, Value: rs.jdbc
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] project created from Old (1.x) Archetype in dir: C:\rs.jdbc\jdbciam
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  01:07 min
[INFO] Finished at: 2024-03-20T09:24:28Z
[INFO] ------------------------------------------------------------------------

C:\rs.jdbc>cd jdbciam

在 pom.xml 中添加以下模块及依赖:

<repositories>
    <repository>
      <id>redshift</id>
      <url>http://redshift-maven-repository.s3-website-us-east-1.amazonaws.com/release</url>
    </repository>
</repositories>


<dependency>
        <groupId>com.amazon.redshift</groupId>
        <artifactId>redshift-jdbc42</artifactId>
        <version>2.1.0.26</version>
     </dependency>
<dependency>
      <groupId>com.amazonaws</groupId>
      <artifactId>aws-java-sdk-core</artifactId>
      <version>1.12.23</version>
      <scope>runtime</scope>
      <optional>true</optional>
</dependency>
    <dependency>
      <groupId>com.amazonaws</groupId>
      <artifactId>aws-java-sdk-redshift</artifactId>
      <version>1.12.23</version>
      <scope>runtime</scope>
      <optional>true</optional>
</dependency>
<dependency>
      <groupId>com.amazonaws</groupId>
      <artifactId>aws-java-sdk-sts</artifactId>
      <version>1.12.23</version>
      <scope>runtime</scope>
      <optional>true</optional>
</dependency>


	<plugin>
       <artifactId>maven-assembly-plugin</artifactId>
       <configuration>
		<archive>
		<manifest>
		    <mainClass>rs.jdbc.rsiam</mainClass>
		</manifest>
		</archive>
           <descriptorRefs>
               <descriptorRef>jar-with-dependencies</descriptorRef>
           </descriptorRefs>
       </configuration>
     </plugin>

接下来,用文本编辑器在 C:\rs.jdbc\jdbciam\src\main\java\rs\jdbc 路径创建 rsiam.java 文件并写入以下代码(请注意,在 Url 中指定使用 iam 方式:jdbc:redshift:iam; 相关属性通过 setProperty实现):

C:\rs.jdbc\jdbciam>type src\main\java\rs\jdbc\rsiam.java
package rs.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import java.sql.ResultSet;

public class rsiam {
    static final String redshiftUrl = "jdbc:redshift:iam://cluster-identifier.cm4u43pp7hs1.cn-northwest-1.redshift.amazonaws.com.cn:5439/mydb";

    static final String SQL_SELECT =
                "select * from marketing.employee;";
    static ResultSet rs = null;

    public static void main(String[] args) {
        Connection connection = null;
        Statement statement = null;

        try {
           Class.forName("com.amazon.redshift.jdbc42.Driver");
           Properties properties = new Properties();
           properties.setProperty("IdP_Port", "443");
           properties.setProperty("Plugin_Name", "com.amazon.redshift.plugin.AdfsCredentialsProvider");
           properties.setProperty("SSL", "true");
           properties.setProperty("loginToRp", "urn:amazon:webservices:cn-north-1");
           properties.setProperty("IdP_Host", "dc.adfs.cn");
           properties.setProperty("Region", "cn-northwest-1");
           properties.setProperty("SSL_Insecure", "true");
           properties.setProperty("Password", " MyPassword123(");
           properties.setProperty("User", "bob@adfs.cn");

           connection = DriverManager.getConnection(redshiftUrl, properties);
           try {
                statement = connection.createStatement();
                rs = statement.executeQuery(SQL_SELECT);
                        if (rs != null) {
                                while (rs.next()) {
                                        System.out.print("Employee ID: " +
                                                rs.getInt("id"));
                                        System.out.print(",Employee Name: " +
                                                rs.getString("name"));
                                        System.out.print(", Employee Age: " +
                                                rs.getInt("age"));
                                        System.out.println();
                                }
                        }
                } catch (SQLException ex) {
                        System.out.println(ex.getMessage());
                }
        } catch(ClassNotFoundException cnfe) {
            cnfe.printStackTrace();
        } catch (SQLException sqle) {
            sqle.printStackTrace();
        }
    }
}

打包 jar 并运行,可以返回 employee 表中的数据:

C:\rs.jdbc\jdbciam>mvn clean compile assembly:single
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------------< rs.jdbc:jdbciam >---------------------------
[INFO] Building jdbciam 1.0-SNAPSHOT
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
……
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  9.771 s
[INFO] Finished at: 2024-03-20T13:58:18Z
[INFO] ------------------------------------------------------------------------

C:\rs.jdbc\jdbciam> java -jar target\jdbciam-1.0-SNAPSHOT-jar-with-dependencies.jar
Employee ID: 1,Employee Name: Bob    , Employee Age: 20

在 ADFS 服务器上借助抓包工具可以看到各组件之间的通讯使用了 TLSv1.3。如下图:

总结

TLS 1.1 及之前的版本存在许多严重漏洞,并且没有修复补丁可用。本方案基于 TLS1.2/TLS1.3 部署,可以有效地规避以上漏洞带来的风险。与此同时,本方案的实施使原本需管理的三个系统用户(域用户、亚马逊控制台用户、Redshift 数据库用户)简化为只需管理域用户及组,在显著提高了维护效率的同时也降低了运营成本。

本篇作者

白国栋

西云数据资深技术支持工程师,拥有超过 15 年的数据库行业经验。曾设计并交付电信、金融、电商等行业大型分布式数据库集群,是亚马逊云科技 RDS Oracle,Aurora,Redshift 等多个领域的专家。擅于深挖客户遇到的各类云上疑难问题,始终追求彻底解决问题的卓越标准。