如何為Kafka設定OAuth2安全機制?

banq發表於2018-12-21

隨著Kafka版本2.0.0 的KIP-255(Kafka改進提案)的提交,現在我們可以使用SASL(簡單認證和安全層)OAUTHBEARER來驗證客戶端到代理或中間人身份驗證。

先決條件
  • Docker
  • Docker-compose
  • Git

實現Java類支援OAuth機制
有了KIP-255的文件,我們需要實現2個類來使用外部OAuth2伺服器來驗證我們的客戶端或代理。
第一個類實現AuthenticateCallbackHandler,並將為需要進行身份驗證的客戶端或代理服務。


public class OauthAuthenticateLoginCallbackHandler implements AuthenticateCallbackHandler {
    private final Logger log = LoggerFactory.getLogger(OauthAuthenticateLoginCallbackHandler.class);
    private Map<String, String> moduleOptions = null;
    private boolean configured = false;

    @Override
    public void configure(Map<String, ?> map, String saslMechanism, List<AppConfigurationEntry> jaasConfigEntries) {
        if (!OAuthBearerLoginModule.OAUTHBEARER_MECHANISM.equals(saslMechanism))
            throw new IllegalArgumentException(String.format("Unexpected SASL mechanism: %s", saslMechanism));
        if (Objects.requireNonNull(jaasConfigEntries).size() != 1 || jaasConfigEntries.get(0) == null)
            throw new IllegalArgumentException(
                    String.format("Must supply exactly 1 non-null JAAS mechanism configuration (size was %d)",
                            jaasConfigEntries.size()));
        this.moduleOptions = Collections.unmodifiableMap((Map<String, String>) jaasConfigEntries.get(0).getOptions());
        configured = true;
    }

    public boolean isConfigured(){
        return this.configured;
    }

    @Override
    public void close() {
    }

    @Override
    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
        if (!isConfigured())
            throw new IllegalStateException("Callback handler not configured");
        for (Callback callback : callbacks) {
            if (callback instanceof OAuthBearerTokenCallback)
                try {
                    handleCallback((OAuthBearerTokenCallback) callback);
                } catch (KafkaException e) {
                    throw new IOException(e.getMessage(), e);
                }
            else
                throw new UnsupportedCallbackException(callback);
        }
    }

    private void handleCallback(OAuthBearerTokenCallback callback){
        if (callback.token() != null)
            throw new IllegalArgumentException("Callback had a token already");

        log.info("Try to acquire token!");
        OauthBearerTokenJwt token = OauthHttpCalls.login(null);
        log.info("Retrieved token..");
        if(token == null){
            throw new IllegalArgumentException("Null token returned from server");
        }
        callback.token(token);
    }

}


第二個類實現相同的類,能讓Kafka使用OAuth令牌自查來驗證傳送令牌。


public class OauthAuthenticateValidatorCallbackHandler implements AuthenticateCallbackHandler {
    private final Logger log = LoggerFactory.getLogger(OauthAuthenticateValidatorCallbackHandler.class);
    private List<AppConfigurationEntry> jaasConfigEntries;
    private Map<String, String> moduleOptions = null;
    private boolean configured = false;
    private Time time = Time.SYSTEM;

    @Override
    public void configure(Map<String, ?> map, String saslMechanism, List<AppConfigurationEntry> jaasConfigEntries) {
        if (!OAuthBearerLoginModule.OAUTHBEARER_MECHANISM.equals(saslMechanism))
            throw new IllegalArgumentException(String.format("Unexpected SASL mechanism: %s", saslMechanism));
        if (Objects.requireNonNull(jaasConfigEntries).size() != 1 || jaasConfigEntries.get(0) == null)
            throw new IllegalArgumentException(
                    String.format("Must supply exactly 1 non-null JAAS mechanism configuration (size was %d)",
                            jaasConfigEntries.size()));
        this.moduleOptions = Collections.unmodifiableMap((Map<String, String>) jaasConfigEntries.get(0).getOptions());
        configured = true;
    }

    public boolean isConfigured(){
        return this.configured;
    }

    @Override
    public void close() {
    }

    @Override
    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
        if (!isConfigured())
            throw new IllegalStateException("Callback handler not configured");
        for (Callback callback : callbacks) {
            if (callback instanceof OAuthBearerValidatorCallback)
                try {
                    OAuthBearerValidatorCallback validationCallback = (OAuthBearerValidatorCallback) callback;
                    handleCallback(validationCallback);
                } catch (KafkaException e) {
                    throw new IOException(e.getMessage(), e);
                }
            else
                throw new UnsupportedCallbackException(callback);
        }
    }

    private void handleCallback(OAuthBearerValidatorCallback callback){
        String accessToken = callback.tokenValue();
        if (accessToken == null)
            throw new IllegalArgumentException("Callback missing required token value");

        log.info("Trying to introspect Token!");
        OauthBearerTokenJwt token = OauthHttpCalls.introspectBearer(accessToken);
        log.info("Trying to introspected");

        // Implement Check Expire Token..
        long now = time.milliseconds();
        if(now > token.expirationTime()){
            OAuthBearerValidationResult.newFailure("Expired Token, needs refresh!");
        }

        log.info("Validated! token..");
        callback.token(token);
    }

}


server.properties檔案
在這個檔案中,我們將設定將用於在OAuth2伺服器中進行登入和驗證的類。完整的server.properties檔案位於GitHub儲存庫中。

# ###########################的OAuth類################### ##########
listener.name.sasl_plaintext.oauthbearer.sasl.login.callback.handler.class = br.com.jairsjunior.security.oauthbearer.OauthAuthenticateLoginCallbackHandler
listener.name.sasl_plaintext.oauthbearer.sasl.server.callback.handler.class = br.com.jairsjunior.security.oauthbearer.OauthAuthenticateValidatorCallbackHandler



啟動OAuth2伺服器和我們的Kafka
此專案需要OAuth2伺服器來提供客戶端或代理的令牌和驗證。一個簡單而開源的替代方案是使用ORY Hydra,這是一個用Go編寫的認證OAuth2伺服器。對於此示例,我們使用docker-compose檔案來設定伺服器並建立3個帳戶:

  • consumer-kafka:用於消費者容器
  • producer-kafka:用於生產者容器
  • broker-kafka:用於interbroker身份驗證

為了啟動我們的OAuth2伺服器和我們的Kafka代理,我們需要克隆kafka-playground GitHub儲存庫並在根資料夾中執行docker-compose檔案。在執行docker-compose之前,我們需要設定一個名為HOST_IP的環境變數。

HOST_IP=XXX.XXX.XXX.XXX docker-compose up



配置我們的客戶端(生產者/消費者/流)
在Git的儲存庫中,我們有一個名為kafka-using-java的資料夾,它包含一個生成器示例,使用我們的.jar檔案。 要執行此示例,您需要設定HOST_IP環境變數,其中包含正在執行的計算機的IP地址。

HOST_IP=XXX.XXX.XXX.XXX docker-compose up


原始碼: Github
 

相關文章