Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v8.8.0 breaks authentication #23

Closed
robtesch opened this issue Nov 8, 2023 · 14 comments
Closed

v8.8.0 breaks authentication #23

robtesch opened this issue Nov 8, 2023 · 14 comments
Assignees

Comments

@robtesch
Copy link

robtesch commented Nov 8, 2023

I am elastic cloud customer with an active production cluster. Connecting to the cluster from my application works well when using v8.7.0. When upgrading to v8.8.0, everything still worked on my local machine, but authentication broke completely in production.

Example code of how I am creating a client:

<?php

namespace App\Elasticsearch;

use Elastic\Elasticsearch\Client;
use Elastic\Elasticsearch\ClientBuilder;

class ExampleClass
{

    protected Client $client;

    public function __construct()
    {
        //The below is just an example.
        //Real config is pulled from environment variables.
        $config = [
            'hosts' => [
                'https://my-elastic-cloud-url.com',
            ],
            'basicAuthentication' => [
                'elastic',
                'myPassword',
            ],
        ];
        $this->client = ClientBuilder::fromConfig($config);
    }

    public function checkIndexExists(string $indexName): bool
    {
        $params = [
            'index' => $indexName,
        ];

        //Auth error thrown!
        return $this->client->indices()
                            ->exists($params)
                            ->asBool();
    }
}

The error thrown looks like this:

Unexpected error: 401 Unauthorized: {"error":{"root_cause":[{"type":"security_exception","reason":"unable to authenticate user [elastic] for REST request [/my_index_1/_search]","header":{"WWW-Authenticate":["Basic realm=\"security\" charset=\"UTF-8\"","Bearer realm=\"security\"","ApiKey"]}}],"type":"security_exception","reason":"unable to authenticate user [elastic] for REST request [/my_index_1/_search]","header":{"WWW-Authenticate":["Basic realm=\"security\" charset=\"UTF-8\"","Bearer realm=\"security\"","ApiKey"]}},"status":401}

Reverting back to v8.7.0 everything works as normal again.

EDIT:

It may be worth noting that the actual "host" string is passed in a bit like this:

https://elastic:myPassword@my-production-cluster.es.eu-west-2.aws.cloud.es.io:1234

It could be that the new path or userInfo support doesn't play well with this approach.

@ezimuel
Copy link
Contributor

ezimuel commented Nov 8, 2023

@robtesch I'm trying to reproduce the issue using Elastic Cloud. I just used a test cluster with Elasticsearch v8.8.1 and elasticsearch-php v8.10.0 with the following code:

use Elastic\Elasticsearch\ClientBuilder;
use Psr\Http\Message\RequestInterface;

require 'vendor/autoload.php';

$config = [
    'hosts' => [
        'https://xxx.es.us-central1.gcp.cloud.es.io',
    ],
    'basicAuthentication' => [
        'elastic',
        'password',
    ]
];

$client = ClientBuilder::fromConfig($config);
$result = $client->info();
printf("%s\n", $result->asString());
$request = $client->getTransport()->getLastRequest();
printRequest($request);

function printRequest(RequestInterface $request): void
{
    printf(
        "%s %s %s/%s\n", 
        $request->getMethod(), 
        $request->getUri()->getPath(), 
        strtoupper($request->getUri()->getScheme()), 
        $request->getProtocolVersion()
    );
    foreach ($request->getHeaders() as $name => $value) {
        printf("%s: %s\n", $name, implode(',', $value));
    }
    if ($request->getUri()->getUserInfo()) {
        printf("Authorization: Basic %s\n", base64_encode($request->getUri()->getUserInfo()));
    }
    printf("\n");
    printf("%s\n", $request->getBody()->getContents());
} 

This works fine and the output is as follows:

{
  "name" : "instance-...",
  "cluster_name" : "...",
  "cluster_uuid" : "...",
  "version" : {
    "number" : "8.8.1",
    "build_flavor" : "default",
    "build_type" : "docker",
    "build_hash" : "...",
    "build_date" : "...",
    "build_snapshot" : false,
    "lucene_version" : "9.6.0",
    "minimum_wire_compatibility_version" : "7.17.0",
    "minimum_index_compatibility_version" : "7.0.0"
  },
  "tagline" : "You Know, for Search"
}
GET / HTTPS/1.1
Host: xxx.es.us-central1.gcp.cloud.es.io
Accept: application/vnd.elasticsearch+json; compatible-with=8
User-Agent: elasticsearch-php/8.10.0 (Linux 6.2.0-36-generic; PHP 8.2.12)
x-elastic-client-meta: es=8.10.0,php=8.2.12,t=8.8.0,a=0,sy=v6.3.7
Authorization: Basic ZWxhc3RpYzpwYXNzd29yZA==

Even if I insert the username and password directly in the URL it works, like this:

$config = [
    'hosts' => [
        'https://elastic:password@xxx.es.us-central1.gcp.cloud.es.io',
    ]
];

Can you try to execute the previous code with the two cases (with an without the credentials in the URL)? Thanks!

@ezimuel ezimuel self-assigned this Nov 8, 2023
@robtesch
Copy link
Author

robtesch commented Nov 8, 2023

OK I think this was user error, which this change just brought to the fore. I actually had an old password in the host string, and was supplying the correct password in the basicAuth part like this:

$config = [
            'hosts' => [
                'https://elastic:myOldPassword@my-production-cluster.es.eu-west-2.aws.cloud.es.io:1234',
            ],
            'basicAuthentication' => [
                'elastic',
                'myActualPassword',
            ],
        ];

@robtesch robtesch closed this as completed Nov 8, 2023
@Offek
Copy link

Offek commented Nov 8, 2023

I've also been experiencing issues in the 8.8.0 release.
When attempting to search from an alias I get the following error message:

"message": "400 Bad Request: {\"error\":\"no handler found for uri [/INDEX_NAME/_search/ALIAS_NAME/_search] and method [POST]\"}",
"exception": "Elastic\\Elasticsearch\\Exception\\ClientResponseException"
...

Seems like a wrong uri is being generated.
Downgrading to 8.7.0 resolved the issue.

@ezimuel
Copy link
Contributor

ezimuel commented Nov 8, 2023

OK I think this was user error, which this change just brought to the fore. I actually had an old password in the host string, and was supplying the correct password in the basicAuth part like this:

@robtesch you cannot have user and password in the host URL and in the basicAuthentication. You need to choose only one place for using the authentication (URL or `basicAuthentication').

@ezimuel ezimuel reopened this Nov 8, 2023
@ezimuel
Copy link
Contributor

ezimuel commented Nov 8, 2023

@Offek, can you provide me a code example that generates the error? Which version of Elasticsearch server and elasticsearch-php are you using? Are you using username and password like @robtesch?
Thanks.

@robtesch
Copy link
Author

robtesch commented Nov 8, 2023

OK I think this was user error, which this change just brought to the fore. I actually had an old password in the host string, and was supplying the correct password in the basicAuth part like this:

@robtesch you cannot have user and password in the host URL and in the basicAuthentication. You need to choose only one place for using the authentication (URL or `basicAuthentication').

Yeah, that's why I say I think it was user error. However, this apparently did work before with v8.7. In my case with 8.7 it was ignoring the username/password in the URL and only using the one from basicAuthentication. My suspicion is that v8.8 changed that behaviour in some way (i.e. it preferred the URL username/password instead). I have now removed the username/password from URL and it seems to be working.

@Offek
Copy link

Offek commented Nov 8, 2023

@ezimuel I also basicAuthentication.
The Elasticsearch server version is 8.6.2 and the elasticsearch-php version is 8.10.0.
Here is a sample code:

$client = ClientBuilder::create()
  ->setSSLVerification(false)
  ->setBasicAuthentication(env('USER'),env('PASSWORD'))
  ->setHosts([env('URL')])
  ->build();
  
$postData = [
  'index' => 'ALIAS_NAME',
  'body'  => [
    "from" => $offset,
    "size" => $limit,
    "query" => [
      ...
    ]
  ]
]

$results = $client->search($postData);

As I previously mentioned, this works using elastic-transport-php 8.7.0 but fails on 8.8.0.
Hope this helps!
Thanks

@ezimuel
Copy link
Contributor

ezimuel commented Nov 9, 2023

@Offek, in env('URL') do you have also user:password syntax? If so, you need to remove it and use only `setBasicAuthentication'.

@ezimuel
Copy link
Contributor

ezimuel commented Nov 9, 2023

My suspicion is that v8.8 changed that behaviour in some way (i.e. it preferred the URL username/password instead). I have now removed the username/password from URL and it seems to be working.

@robtesch This is exactly what's going on. The user:password syntax in the URL has priority (here) over the setBasicAuthentication() function (here). Maybe, I need to check if there are both and I should generate an Exception since it's clearly a configuration error.

@Offek
Copy link

Offek commented Nov 9, 2023

@ezimuel I don’t have the user:password syntax in the URL. But it does seem the URL has the index name rather than the alias name. Here is the format I have:

URL=http://DOMAIN/INDEX_NAME/_search

@ezimuel
Copy link
Contributor

ezimuel commented Nov 9, 2023

@Offek I think the issue is not related to the authentication but to the #21 PR. Now, we support the path in the URL host (e.g. http://domain/foo). This is needed if your Elasticsearch server is hosted using a path and not just a domain.
Can you check in the env('URL') if you have a path or only a domain?
Moreover, can you execute the following printRequest() function and copy & paste the output?
Note: I removed the Authorization header and the body for privacy reason but please double check the output to avoid exposing sensitive information. Thanks!

// here the $results = $client->search($postData);
$request = $client->getTransport()->getLastRequest();
printRequest($request);

function printRequest(RequestInterface $request): void
{
    printf(
        "%s %s %s/%s\n", 
        $request->getMethod(), 
        $request->getUri()->getPath(), 
        strtoupper($request->getUri()->getScheme()), 
        $request->getProtocolVersion()
    );
    foreach ($request->getHeaders() as $name => $value) {
        printf("%s: %s\n", $name, implode(',', $value));
    }
    if ($request->getUri()->getUserInfo()) {
        printf("Authorization: Basic ...\n");
    }
    printf("\n");
    printf("Here the body...\n");
} 

@Offek
Copy link

Offek commented Nov 10, 2023

@ezimuel I think the #21 PR makes sense. My URL variable is in the following format:

http://IP:PORT/INDEX_NAME/_search

Regarding the request execution, I'm currently away, will do my best to send it to you this weekend!

@Offek
Copy link

Offek commented Nov 12, 2023

Hi @ezimuel , I tried running your supplied code, however I was not able to do that since the script crashes on this line:

$results = $client->search($postData);

However, I removed /INDEX_NAME/_search from the URL variable and now everything seems to work. So the new format is simply like this:

http://IP:PORT

Prior to 8.8.0 it worked fine, starting 8.8.0, I have to use the new format.
Thanks for your help!

@ezimuel
Copy link
Contributor

ezimuel commented Nov 12, 2023

@Offek I see, I didn't understand before. The URL to use in the setHosts() must be only the base URL of Elasticsearch, e.g. localhost:9200. The /INDEX_NAME/_search should not be used as host since this is the specific path of the search() API. Basically, an Elasticsearch endpoint has a specific path, for instance the index() API uses /{index}/_doc and the final URL become localhost:9200/{index}/_doc. This path is automatically generated by the elasticsearch-php client. You can see an example of this implementation here.

Before elastic-transport-php v8.8.0 didn't use the path specified in the host, that's why it used to works before. Anyway, I'm glad that you solved the issue.

@ezimuel ezimuel closed this as completed Nov 12, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants