Как мы зашифровали net/rpc

    Мы проектировали агента для бекапов. Агент узнает у бекенда что бекапить и отправляет данные в хранилище. Злая врезка в канал и подмена адреса хранилища катастрофична.

    HTTPS протестировали в первом подходе. Было ощущение, что можно сделать проще. Внутренние сервисы начинали масштабироваться по датацентрам. Хотелось сделать надежное решение для агента и внутренних сервисов. Без туннелей и HTTPS.

    В итоге заменили HTTPS на net/rpc + crypto/tls.


    Кроме прозрачного расширения, это дает возможность сделать свой корневой сертификат. Подписывать сертификаты сервисов и вшить проверку на клиентской стороне. Это можно сделать и в HTTPS, но удобство RPC подкупает.

    Корневой сертификат.
    openssl req -newkey rsa:2048 -x509 -new -keyout CA.key -days 10000 -out CA.crt
    


    Генерируем сертификат и прошиваем DNS адрес сервиса.
    openssl req -newkey rsa:2048 -nodes -keyout server.key -out server.csr
    openssl x509 -req -in server.csr -CA CA.crt -CAkey CA.key -CAcreateserial -out server.pem -days 10000 -extensions v3_req -extfile backend.cnf
    


    Конфиг OpenSSL — backend.cnf
    [v3_req]
    keyUsage = keyEncipherment, dataEncipherment
    extendedKeyUsage = serverAuth
    subjectAltName = @alt_names
    
    [alt_names]
    DNS.1 = backend.rollbackup.ru
    


    Через generate_cert.go можно быстро сгенерировать сертификат без CA.

    Загружаем сертификат и приватный ключ. Оборачиваем rpc в tls.Server:
    import "crypto/tls"
    
    cert, _ := tls.LoadX509KeyPair("server.pem", "server.key")
    conn, _ := tls.Listen("tcp", "backend.rollbackup.ru:9001", &tls.Config{Certificates: []tls.Certificate{cert}})
    
    server := rpc.NewServer()
    for {
    	if conn, err := l.Accept(); err == nil {
    		go server.ServeCodec(rpc.NewServerCodec(conn))
    	}
    }
    


    На клиенте фиксируем поддержку нашего корневого сертификата.
    import "crypto/x509"
    import "crypto/tls"
    import "net/rpc"
    
    // корневой сертификат ...
    cert := `-----BEGIN CERTIFICATE----- ...`
    
    rootCAs := x509.NewCertPool()
    rootCAs.AppendCertsFromPEM([]byte(cert))
    
    conn, _ := tls.Dial("tcp", "backend.rollbackup.ru:9001", &tls.Config{RootCAs: rootCAs})
    client := rpc.NewClient(conn)
    


    Если подменить DNS и поднять злой сервер, клиент не установит соединение.
    agent connection: x509: certificate is valid for backend.rollbackup.ru, not backend.brazzers.com
    


    Бонус трек
    Усиливаем безопасность: оставляем сильные шифры и убираем поддержку TLS < 1.2. Актуально для Go 1.3.
    tls.Config{
    // ...
    	CipherSuites: []uint16{
    		tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
    		tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
    	},
    	MinVersion: tls.VersionTLS12
    })
    


    Мы храним сертификаты на физическом токене. Чего и вам желаем.

    Наша простая библиотека-обертка на Github: secrpc. Примеры использования в агенте.
    • +12
    • 5,8k
    • 1
    Поделиться публикацией

    Комментарии 1

      0
      Мы храним сертификаты на физическом токене. Чего и вам желаем.

      Вот это грамотное решение, ведь преимущества использования токенов в подавляющем большинстве случаев перевешивают их недостатки.

      Секретные ключи для сертификатов верхних уровней во внутренней иерархии PKI в организации вообще не должны покидать защищенных носителей (и подпись дочерних сертификатов тоже должна выполняться внутри самого криптоустройства).

      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

      Самое читаемое